summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--include/Makefile.am21
-rw-r--r--include/opkele/basic_rp.h218
-rw-r--r--include/opkele/discovery.h88
-rw-r--r--include/opkele/exception.h19
-rw-r--r--include/opkele/extension.h7
-rw-r--r--include/opkele/extension_chain.h6
-rw-r--r--include/opkele/prequeue_rp.h81
-rw-r--r--include/opkele/sreg.h8
-rw-r--r--include/opkele/types.h171
-rw-r--r--include/opkele/util.h8
-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
22 files changed, 1366 insertions, 299 deletions
diff --git a/include/Makefile.am b/include/Makefile.am
index 51dcea1..50fcb62 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,21 +1,32 @@
-nobase_include_HEADERS = \
+NODIST_HEADERS_ = \
opkele/acconfig.h \
+ opkele/tr1-mem.h
+
+nobase_include_HEADERS = \
opkele/opkele-config.h \
opkele/types.h \
opkele/association.h \
opkele/exception.h \
opkele/server.h \
opkele/consumer.h \
opkele/extension.h \
opkele/sreg.h \
opkele/extension_chain.h \
opkele/xconsumer.h \
opkele/xserver.h \
- opkele/discovery.h \
opkele/uris.h \
- opkele/tr1-mem.h
-EXTRA_DIST = \
+ opkele/tr1-mem.h \
+ opkele/basic_rp.h \
+ opkele/prequeue_rp.h \
+ opkele/iterator.h \
+ ${NODIST_HEADERS_}
+
+noinst_HEADERS = \
opkele/data.h \
opkele/curl.h opkele/expat.h opkele/tidy.h \
opkele/util.h \
- opkele/debug.h
+ opkele/debug.h \
+ opkele/discovery.h
+
+dist-hook:
+ rm -f $(addprefix ${distdir}/,${NODIST_HEADERS_})
diff --git a/include/opkele/basic_rp.h b/include/opkele/basic_rp.h
new file mode 100644
index 0000000..3f17fd9
--- a/dev/null
+++ b/include/opkele/basic_rp.h
@@ -0,0 +1,218 @@
+#ifndef __OPKELE_BASIC_RP_H
+#define __OPKELE_BASIC_RP_H
+
+#include <cstring>
+#include <string>
+#include <opkele/types.h>
+#include <opkele/extension.h>
+
+namespace opkele {
+ using std::string;
+
+ struct openid_endpoint_t {
+ string uri;
+ string claimed_id;
+ string local_id;
+
+ openid_endpoint_t() { }
+ openid_endpoint_t(const string& u,const string& cid,const string& lid)
+ : uri(u), claimed_id(cid), local_id(lid) { }
+
+ bool operator==(const openid_endpoint_t& x) const {
+ return uri==x.uri && local_id==x.local_id; }
+ bool operator<(const openid_endpoint_t& x) const {
+ int c;
+ return (c=strcmp(uri.c_str(),x.uri.c_str()))
+ ? (c<0) : (strcmp(local_id.c_str(),x.local_id.c_str())<0); }
+ };
+
+ class basic_RP {
+ public:
+
+ virtual ~basic_RP() { }
+
+ /**
+ * @name Global persistent store API
+ * These are functions related to the associations with OP storage
+ * and retrieval and nonce records. They provide an interface to
+ * the persistent storage which is shared by all sessions. If the
+ * implementor prefers the dumb mode instead, the function should
+ * throw dumb_RP exception instead.
+ * @see opkele::dumb_RP
+ * @{
+ */
+ /**
+ * Store association and return allocated association object.
+ * @param OP OP endpoint
+ * @param handle association handle
+ * @param type association type
+ * @param secret association secret
+ * @params expires_in the number of seconds association expires in
+ * @return the association object
+ * @throw dumb_RP for dumb RP
+ */
+ virtual assoc_t store_assoc(
+ const string& OP,const string& handle,
+ const string& type,const secret_t& secret,
+ int expires_in) = 0;
+ /**
+ * Find valid unexpired association with an OP.
+ * @param OP OP endpoint URL
+ * @return association found
+ * @throw failed_lookup if no association found
+ * @throw dumb_RP for dumb RP
+ */
+ virtual assoc_t find_assoc(
+ const string& OP) = 0;
+ /**
+ * Retrieve valid association handle for an OP by handle.
+ * @param OP OP endpoint URL
+ * @param handle association handle
+ * @return association found
+ * @throw failed_lookup if no association found
+ * @throw dumb_RP for dumb RP
+ */
+ virtual assoc_t retrieve_assoc(
+ const string& OP,const string& handle) = 0;
+ /**
+ * Invalidate association with OP
+ * @param OP OP endpoint URL
+ * @param handle association handle
+ * @throw dumb_RP for dumb RP
+ */
+ virtual void invalidate_assoc(const string& OP,const string& handle) = 0;
+
+ /**
+ * Check the nonce validity. That is, check that we haven't
+ * accepted request with this nonce from this OP, yet. May involve
+ * cutting off by the timestamp and checking the rest against the
+ * store of seen nonces.
+ * @param OP OP endpoint URL
+ * @param nonce nonce value
+ * @throw id_res_bad_nonce if the nonce is not to be accepted, i.e.
+ * either too old or seen.
+ */
+ virtual void check_nonce(const string& OP,const string& nonce) = 0;
+ /**
+ * @}
+ */
+
+ /**
+ * @name Session persistent store API
+ * @{
+ */
+ /**
+ * Retrieve OpenID endpoint being currently used for
+ * authentication. If there is no endpoint available, throw a
+ * no_endpoint exception.
+ * @return reference to the service endpoint object
+ * @see next_endpoint
+ * @throw no_endpoint if no endpoint available
+ */
+ virtual const openid_endpoint_t& get_endpoint() const = 0;
+ /**
+ * Advance to the next endpoint to try.
+ * @see get_endpoint()
+ * @throw no_endpoint if there are no more endpoints
+ */
+ virtual void next_endpoint() = 0;
+ /**
+ * @}
+ */
+
+ /**
+ * @name Site particulars API
+ * @{
+ */
+ /**
+ * Return an absolute URL of the page being processed, includining
+ * query parameters. It is used to validate return_to URL on
+ * positive assertions.
+ * @return fully qualified url of the page being processed.
+ */
+ virtual const string get_this_url() const = 0;
+ /**
+ * @}
+ */
+
+ /**
+ * @name OpenID actions
+ * @{
+ */
+ /**
+ * Initiates authentication session, doing discovery, normalization
+ * and whatever implementor wants to do at this point.
+ * @param usi User-supplied identity
+ */
+ virtual void initiate(const string& usi) = 0;
+ /**
+ * Prepare checkid_request.
+ * @param rv reference to the openid message to prepare
+ * @param mode checkid_setup or checkid_immediate
+ * @param return_to the URL OP should redirect to after completion
+ * @param realm authentication realm to pass to OP
+ * @param ext pointer to extension to use in request preparation
+ * @return reference to the openid message
+ */
+ basic_openid_message& checkid_(
+ basic_openid_message& rv,
+ mode_t mode,
+ const string& return_to,const string& realm,
+ extension_t *ext=0);
+ /**
+ * Verify assertion at the end of round-trip.
+ * @param om incoming openid message
+ * @param ext pointer to extention to use in parsing assertion
+ * @throw id_res_setup if checkid_immediate request could not be
+ * completed
+ * @throw id_res_cancel if authentication request was canceled
+ * @throw id_res_mismatch in case of signature mismatch
+ * @throw id_res_bad_return_to if return_to url seems to be
+ * tampered with
+ * @throw id_res_unauthorized if OP is not authorized to make
+ * assertions regarding the identity
+ */
+ void id_res(const basic_openid_message& om,extension_t *ext=0);
+
+ /**
+ * Establish association with OP
+ * @param OP OP to establish association with
+ * @throw dumb_RP if for a dumb RP
+ */
+ virtual assoc_t associate(const string& OP);
+ /**
+ * Check authentication with OP and invalidate handle if requested
+ * and confirmed
+ * @param OP OP to check with
+ * @param om message to check
+ * @throw failed_check_authentication if OP fails to confirm
+ * authenticity of the assertion
+ */
+ void check_authentication(const string& OP,const basic_openid_message& om);
+ /**
+ * @}
+ */
+
+ /**
+ * @name Miscellanea
+ * @{
+ */
+ /**
+ * Verify OP authority. Return normally if OP is authorized to make
+ * an assertion, throw an exception otherwise.
+ * @param OP OP endpoint
+ * @param claimed_id claimed identity
+ * @param identity OP-Local identifier
+ * @throw id_res_unauthorized if OP is not authorized to make
+ * assertion regarding this identity.
+ */
+ virtual void verify_OP(const string& OP,
+ const string& claimed_id,const string& identity) const = 0;
+ /**
+ * @}
+ */
+ };
+
+}
+
+#endif /* __OPKELE_BASIC_RP_H */
diff --git a/include/opkele/discovery.h b/include/opkele/discovery.h
index af4aa29..ab4b9d9 100644
--- a/include/opkele/discovery.h
+++ b/include/opkele/discovery.h
@@ -1,40 +1,104 @@
#ifndef __OPKELE_DISCOVERY_H
#define __OPKELE_DISCOVERY_H
#include <string>
#include <opkele/types.h>
+#include <opkele/basic_rp.h>
namespace opkele {
using std::string;
- struct idiscovery_t;
+ namespace xrd {
- void idiscover(idiscovery_t& result,const string& identity);
+ struct priority_compare {
+ inline bool operator()(long a,long b) const {
+ return (a<0) ? false : (b<0) ? true : (a<b);
+ }
+ };
+
+ template <typename _DT>
+ class priority_map : public multimap<long,_DT,priority_compare> {
+ typedef multimap<long,_DT,priority_compare> map_type;
+ public:
+
+ inline _DT& add(long priority,const _DT& d) {
+ return insert(typename map_type::value_type(priority,d))->second;
+ }
+
+ bool has_value(const _DT& d) const {
+ for(typename map_type::const_iterator i=this->begin();i!=this->end();++i)
+ if(i->second==d) return true;
+ return false;
+ }
+ };
+
+ typedef priority_map<string> canonical_ids_t;
+ typedef priority_map<string> local_ids_t;
+ typedef set<string> types_t;
+ typedef priority_map<string> uris_t;
+
+ class service_t {
+ public:
+ types_t types;
+ uris_t uris;
+ local_ids_t local_ids;
+ string provider_id;
+
+ void clear() {
+ types.clear();
+ uris.clear(); local_ids.clear();
+ provider_id.clear();
+ }
+ };
+ typedef priority_map<service_t> services_t;
+
+ class XRD_t {
+ public:
+ time_t expires;
+
+ canonical_ids_t canonical_ids;
+ local_ids_t local_ids;
+ services_t services;
+ string provider_id;
+
+ void clear() {
+ expires = 0;
+ canonical_ids.clear(); local_ids.clear();
+ services.clear();
+ provider_id.clear();
+ }
+ bool empty() const {
+ return
+ canonical_ids.empty()
+ && local_ids.empty()
+ && services.empty();
+ }
+
+ };
+
+ }
+
+ typedef util::output_iterator_proxy<openid_endpoint_t>
+ endpoint_discovery_iterator;
+
+ string idiscover(
+ endpoint_discovery_iterator oi,
+ const string& identity);
struct idiscovery_t {
bool xri_identity;
string normalized_id;
string canonicalized_id;
xrd::XRD_t xrd;
idiscovery_t() { }
- idiscovery_t(const string& i) {
- idiscover(*this,i);
- }
- idiscovery_t(const char *i) {
- idiscover(*this,i);
- }
void clear() {
normalized_id.clear(); canonicalized_id.clear();
xrd.clear();
}
- idiscovery_t& operator=(const string& i) {
- idiscover(*this,i); return *this; }
- idiscovery_t& operator=(const char *i) {
- idiscover(*this,i); return *this; }
};
}
#endif /* __OPKELE_DISCOVERY_H */
diff --git a/include/opkele/exception.h b/include/opkele/exception.h
index a8c3339..ccb39d9 100644
--- a/include/opkele/exception.h
+++ b/include/opkele/exception.h
@@ -1,316 +1,335 @@
#ifndef __OPKELE_EXCEPTION_H
#define __OPKELE_EXCEPTION_H
/**
* @file
* @brief opkele exceptions
*/
#include <curl/curl.h>
#include <opkele/opkele-config.h>
#ifdef OPKELE_HAVE_KONFORKA
# include <konforka/exception.h>
/**
* the exception parameters declaration
*/
# define OPKELE_E_PARS const string& fi,const string&fu,int l,const string& w
/**
* the exception parameters list to pass to constructor
*/
# define OPKELE_E_CONS_ fi,fu,l,
/**
* the exception codepoint specification
*/
# define OPKELE_CP_ CODEPOINT,
/**
* open function-try-block
*/
# define OPKELE_FUNC_TRY try
/**
* the simple rethrow of konforka-based exception
*/
# define OPKELE_RETHROW catch(konforka::exception& e) { e.see(CODEPOINT); throw; }
#else /* OPKELE_HAVE_KONFORKA */
# include <exception>
# include <string>
/**
* the exception parameter declaration
*/
# define OPKELE_E_PARS const string& w
/**
* the dummy prefix for exception parameters list to prepend in the absence of
* konforka library
*/
# define OPKELE_E_CONS_
/**
* the dummy placeholder for konforka exception codepoint specification
*/
# define OPKELE_CP_
/**
* the dummy define for the opening function-try-block
*/
# define OPKELE_FUNC_TRY
/**
* the dummy define for the konforka-based rethrow of exception
*/
# define OPKELE_RETHROW
#endif /* OPKELE_HAVE_KONFORKA */
/**
* the exception parameters list to pass to constructor
*/
# define OPKELE_E_CONS OPKELE_E_CONS_ w
namespace opkele {
using std::string;
/**
* the base opkele exception class
*/
class exception : public
# ifdef OPKELE_HAVE_KONFORKA
konforka::exception
# else
std::exception
# endif
{
public:
# ifdef OPKELE_HAVE_KONFORKA
explicit
exception(const string& fi,const string& fu,int l,const string& w);
# else /* OPKELE_HAVE_KONFORKA */
string _what;
explicit exception(const string& w);
virtual ~exception() throw();
virtual const char * what() const throw();
# endif /* OPKELE_HAVE_KONFORKA */
};
/**
* thrown in case of failed conversion
*/
class failed_conversion : public exception {
public:
failed_conversion(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown in case of failed lookup (either parameter or persistent store)
*/
class failed_lookup : public exception {
public:
failed_lookup(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown in case of bad input (either local or network)
*/
class bad_input : public exception {
public:
bad_input(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown on failed assertion
*/
class failed_assertion : public exception {
public:
failed_assertion(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown if the handle being retrieved is invalid
*/
class invalid_handle : public exception {
public:
invalid_handle(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown if the handle passed to check_authentication request is not
* stateless
*/
class stateful_handle : public exception {
public:
stateful_handle(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown if check_authentication request fails
*/
class failed_check_authentication : public exception {
public:
failed_check_authentication(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown if the id_res request result is negative
*/
class id_res_failed : public exception {
public:
id_res_failed(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown if the user_setup_url is provided with negative response
*/
class id_res_setup : public id_res_failed {
public:
string setup_url;
id_res_setup(OPKELE_E_PARS,const string& su="")
: id_res_failed(OPKELE_E_CONS), setup_url(su) { }
~id_res_setup() throw() { }
};
/**
* thrown in case of signature mismatch
*/
class id_res_mismatch : public id_res_failed {
public:
id_res_mismatch(OPKELE_E_PARS)
: id_res_failed(OPKELE_E_CONS) { }
};
/**
* thrown if the association has expired before it could've been verified.
*/
class id_res_expired_on_delivery : public id_res_failed {
public:
id_res_expired_on_delivery(OPKELE_E_PARS)
: id_res_failed(OPKELE_E_CONS) { }
};
/**
* thown when the user cancelled authentication process.
*/
class id_res_cancel : public id_res_failed {
public:
id_res_cancel(OPKELE_E_PARS)
: id_res_failed(OPKELE_E_CONS) { }
};
/**
* thrown in case of nonce reuse or otherwise imperfect nonce.
*/
class id_res_bad_nonce : public id_res_failed {
public:
id_res_bad_nonce(OPKELE_E_PARS)
: id_res_failed(OPKELE_E_CONS) { }
};
/**
* thrown if return_to didn't pass verification
*/
class id_res_bad_return_to : public id_res_failed {
public:
id_res_bad_return_to(OPKELE_E_PARS)
: id_res_failed(OPKELE_E_CONS) { }
};
/**
* thrown if OP isn't authorized to make an assertion
*/
class id_res_unauthorized : public id_res_failed {
public:
id_res_unauthorized(OPKELE_E_PARS)
: id_res_failed(OPKELE_E_CONS) { }
};
/**
* openssl malfunction occured
*/
class exception_openssl : public exception {
public:
unsigned long _error;
string _ssl_string;
exception_openssl(OPKELE_E_PARS);
~exception_openssl() throw() { }
};
/**
* network operation related error occured
*/
class exception_network : public exception {
public:
exception_network(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* network operation related error occured, specifically, related to
* libcurl
*/
class exception_curl : public exception_network {
public:
CURLcode _error;
string _curl_string;
exception_curl(OPKELE_E_PARS);
exception_curl(OPKELE_E_PARS,CURLcode e);
~exception_curl() throw() { }
};
/**
* htmltidy related error occured
*/
class exception_tidy : public exception {
public:
int _rc;
exception_tidy(OPKELE_E_PARS);
exception_tidy(OPKELE_E_PARS,int r);
~exception_tidy() throw() { }
};
/**
* exception thrown in case of failed discovery
*/
class failed_discovery : public exception {
public:
failed_discovery(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* unsuccessfull xri resolution
*/
class failed_xri_resolution : public failed_discovery {
public:
long _code;
failed_xri_resolution(OPKELE_E_PARS,long _c=-1)
: failed_discovery(OPKELE_E_CONS), _code(_c) { }
};
/**
* not implemented (think pure virtual) member function executed, signfies
* programmer error
*/
class not_implemented : public exception {
public:
not_implemented(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* internal error, indicates internal libopkele problem
*/
class internal_error : public exception {
public:
internal_error(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
/**
* thrown in case of unsupported parameter encountered (e.g. unsupported
* association type).
*/
class unsupported : public exception {
public:
unsupported(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
+ /**
+ * thrown by associations store related functions in case of dumb RP.
+ */
+ class dumb_RP : public exception {
+ public:
+ dumb_RP(OPKELE_E_PARS)
+ : exception(OPKELE_E_CONS) { }
+ };
+
+ /**
+ * thrown by endpoint-queue related function if endpoint is being
+ * accessed but there's no endpoint available.
+ */
+ class no_endpoint : public exception {
+ public:
+ no_endpoint(OPKELE_E_PARS)
+ : exception(OPKELE_E_CONS) { }
+ };
+
}
#endif /* __OPKELE_EXCEPTION_H */
diff --git a/include/opkele/extension.h b/include/opkele/extension.h
index 513672f..3ee25ee 100644
--- a/include/opkele/extension.h
+++ b/include/opkele/extension.h
@@ -1,65 +1,66 @@
#ifndef __OPKELE_EXTENSION_H
#define __OPKELE_EXTENSION_H
/**
* @file
* @brief extensions framework basics
*/
#include <opkele/types.h>
namespace opkele {
/**
* OpenID extension hooks base class
*/
class extension_t {
public:
virtual ~extension_t() { }
+
/**
* hook called by consumer before submitting data to OpenID server.
* It is supposed to manipulate parameters list.
* @param p parameters about to be submitted to server
* @param identity identity being verified. It may differ from the
* one available in parameters list in case of delegation
* @see consumer_t::checkid_
* @see consumer_t::checkid_immediate
* @see consumer_t::checkid_setup
*/
- virtual void checkid_hook(params_t& p,const string& identity);
+ virtual void checkid_hook(basic_openid_message& om);
/**
* hook called by consumer after identity information received from
* OpenID server is verified.
* @param p parameters received from server
* @param sp signed parameters received from server with 'openid.'
* leader stripped
* @param identity identity confirmed. May differ from the one
* available in parameters list in case of delegation. May also be
* empty which means - extract one from parameters
* @see consumer_t::id_res
*/
- virtual void id_res_hook(const params_t& p,const params_t& sp,const string& identity);
+ virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp);
/**
* hook called by server before returning information to consumer.
* The hook may manipulate output parameters. It is important to
* note that modified pout["signed"] is used for signing response.
* @param pin request parameters list with "openid." prefix
* @param pout response parameters list without "openid." prefix
* @see server_t::checkid_
* @see server_t::checkid_immediate
* @see server_t::checkid_setup
*/
- virtual void checkid_hook(const params_t& pin,params_t& pout);
+ virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum);
/**
* Casts the object to pointer to itself. For convenient passing
* of pointer.
*/
operator extension_t*(void) { return this; }
};
}
#endif /* __OPKELE_EXTENSION_H */
diff --git a/include/opkele/extension_chain.h b/include/opkele/extension_chain.h
index f0eea94..fb9bc84 100644
--- a/include/opkele/extension_chain.h
+++ b/include/opkele/extension_chain.h
@@ -1,38 +1,38 @@
#ifndef __OPKELE_EXTENSION_CHAIN_H
#define __OPKELE_EXTENSION_CHAIN_H
/**
* @file
* @brief extension chain extension
*/
#include <list>
#include <opkele/extension.h>
namespace opkele {
using std::list;
/**
* OpenID extensions chain used to combine extensions, it is actually an
* stl list of pointers to extensions.
*/
class extension_chain_t : public extension_t, public list<extension_t*> {
public:
/**
* Default constructor creates an empty chain
*/
extension_chain_t() { }
/**
* Create extension chain with a single extension in it
*/
extension_chain_t(extension_t *e) { push_back(e); }
- virtual void checkid_hook(params_t& p,const string& identity);
- virtual void id_res_hook(const params_t& p,const params_t& sp,const string& identity);
- virtual void checkid_hook(const params_t& pin,params_t& pout);
+ virtual void checkid_hook(basic_openid_message& om);
+ virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp);
+ virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum);
};
}
#endif /* __OPKELE_EXTENSION_CHAIN_H */
diff --git a/include/opkele/prequeue_rp.h b/include/opkele/prequeue_rp.h
new file mode 100644
index 0000000..b98dd5a
--- a/dev/null
+++ b/include/opkele/prequeue_rp.h
@@ -0,0 +1,81 @@
+#ifndef __OPKELE_RP_H
+#define __OPKELE_RP_H
+
+#include <string>
+#include <set>
+#include <iterator>
+#include <opkele/basic_rp.h>
+
+namespace opkele {
+ using std::string;
+ using std::set;
+ using std::iterator;
+ using std::output_iterator_tag;
+
+ class prequeue_RP : public basic_RP {
+ public:
+ /**
+ * @name Session persistent store API
+ * @{
+ */
+ /**
+ * Called before queueing discovered endpoints. Typically happens
+ * while initiating authentication session.
+ * @see queue_endpoint()
+ * @see end_queueing()
+ */
+ virtual void begin_queueing() { }
+ /**
+ * Used to queue discovered endpoint. It is implementors
+ * responsibility to store the endpoint wherever he choses to store
+ * it.
+ * @param oep the endpoint to queue
+ * @see begin_queueing()
+ * @see end_queueing()
+ */
+ virtual void queue_endpoint(const openid_endpoint_t& oep) = 0;
+ /**
+ * Called after all discovered endpoints were queued. Implementor
+ * may chose to use this virtual to commit endpoints queue to
+ * persistent store.
+ * @see begin_queueing()
+ * @see queue_endpoint()
+ */
+ virtual void end_queueing() { }
+
+ /**
+ * Used to store normalized id when initiating request.
+ * The default implementation does nothing, because implementor
+ * doesn't have to care.
+ * @param nid normalized id
+ * @see get_normalzied_id()
+ */
+ virtual void set_normalized_id(const string& nid);
+ /**
+ * Return the normalized id previously set by set_normalized_id().
+ * Provided for the sake of completeness because default
+ * implementation doesn't use it.
+ * @return the normalized identity
+ */
+ virtual const string get_normalized_id() const;
+ /**
+ * @}
+ */
+
+ /**
+ * @name Actions
+ * @{
+ */
+ void initiate(const string& usi);
+
+ /**
+ * @}
+ */
+
+ void verify_OP(const string& OP,
+ const string& claimed_id,const string& identity) const;
+ };
+
+}
+
+#endif /* __OPKELE_RP_H */
diff --git a/include/opkele/sreg.h b/include/opkele/sreg.h
index df37a86..24cb315 100644
--- a/include/opkele/sreg.h
+++ b/include/opkele/sreg.h
@@ -1,203 +1,203 @@
#ifndef __OPKELE_SREG_H
#define __OPKELE_SREG_H
/**
* @file
* @brief Simple registration extension
*/
#include <opkele/extension.h>
namespace opkele {
using std::map;
/**
* OpenID simple registration extension implementation
* http://openid.net/specs/openid-simple-registration-extension-1_0.html
*/
class sreg_t : public extension_t {
public:
/**
* sreg fields enumeration
*/
enum fieldbit_t {
/**
* Any UTF-8 string that the End User wants to use as a nickname.
*/
field_nickname = 1,
/**
* The email address of the End User as specified in section 3.4.1 of [RFC2822]
*/
field_email = 2,
/**
* UTF-8 string free text representation of the End User's full name.
*/
field_fullname = 4,
/**
* The End User's date of birth as YYYY-MM-DD. Any values whose
* representation uses fewer than the specified number of
* digits should be zero-padded. The length of this value MUST
* always be 10. If the End User user does not want to reveal
* any particular component of this value, it MUST be set to
* zero.
*
* For instance, if a End User wants to specify that his date
* of birth is in 1980, but not the month or day, the value
* returned SHALL be "1980-00-00".
*/
field_dob = 8,
/**
* Alias to field_dob
*/
field_birthdate = field_dob,
/**
* The End User's gender, "M" for male, "F" for female.
*/
field_gender = 16,
/**
* Alias to field_gender
*/
field_sex = field_gender,
/**
* UTF-8 string free text that SHOULD conform to the End User's
* country's postal system.
*/
field_postcode = 32,
/**
* The End User's country of residence as specified by ISO3166
*/
field_country = 64,
/**
* End User's preferred language as specified by ISO639
*/
field_language = 128,
/**
* ASCII string from TimeZone database
*
* For example, "Europe/Paris" or "America/Los_Angeles".
*/
field_timezone = 256,
/**
* All fields bits combined
*/
fields_ALL = 511,
/**
* No fields
*/
fields_NONE = 0
};
/**
* Bitmask for fields which, if absent from the response, will
* prevent the Consumer from completing the registration without
* End User interation.
*/
long fields_required;
/**
* Bitmask for fields that will be used by the Consumer, but whose
* absence will not prevent the registration from completing.
*/
long fields_optional;
/**
* A URL which the Consumer provides to give the End User a place
* to read about the how the profile data will be used. The
* Identity Provider SHOULD display this URL to the End User if it
* is given.
*/
string policy_url;
/**
* Bitmask for fields present in response
*/
long has_fields;
/**
* Container type for response fields values
*/
typedef map<fieldbit_t,string> response_t;
/**
* Response contents
*/
response_t response;
/**
* Fields bitmask to send in response
*/
long fields_response;
/**
* Consumer constructor.
* @param fr required fields
* @see fields_required
* @param fo optional fields
* @see fields_optional
* @param pu policy url
* @see policy_url
*/
sreg_t(long fr=fields_NONE,long fo=fields_NONE,const string& pu="")
: fields_required(fr), fields_optional(fo), policy_url(pu), has_fields(0) { }
/**
* Implementation of consumer's checkid hook
*/
- virtual void checkid_hook(params_t& p,const string& identity);
+ virtual void checkid_hook(basic_openid_message& om);
/**
* Implementation of consumer's id_res hook
*/
- virtual void id_res_hook(const params_t& p,const params_t& sp,const string& identity);
+ virtual void id_res_hook(const basic_openid_message& om,const basic_openid_message& sp);
/**
* Implementation of server's checkid_hook
*/
- virtual void checkid_hook(const params_t& pin,params_t& pout);
+ virtual void checkid_hook(const basic_openid_message& inm,basic_openid_message& oum);
/**
* Check and see if we have value for some particular field.
* @param fb field in question
* @see fieldbit_t
* @return true if the value is available
*/
bool has_field(fieldbit_t fb) const { return has_fields&fb; }
/**
* Retrieve the value for a field.
* @param fb field in question
* @see fieldbit_t
* @return field value
* @throw failed_lookup if no data avaialble
*/
const string& get_field(fieldbit_t fb) const;
/**
* Set the value for a field.
* @param fb field in question
* @see fieldbit_t
* @param fv field value
*/
void set_field(fieldbit_t fb,const string& fv);
/**
* Remove the value for a field.
* @param fb field in question
* @see fieldbit_t
*/
void reset_field(fieldbit_t fb);
/**
* Reset field data
*/
void clear();
/**
* Function called after parsing sreg request to set up response
* fields. The default implementation tries to send as much fields
* as we have. The function is supposed to set the data and
* fields_response.
* @see fields_response
* @param pin input request parameters with "openid." prefix
* @param pout output request parameters without "openid." prefix.
* @see checkid_hook(const params_t&,params_t&)
*/
- virtual void setup_response(const params_t& pin,params_t& pout);
+ virtual void setup_response(const basic_openid_message& inm,basic_openid_message& oum);
};
}
#endif /* __OPKELE_SREG_H */
diff --git a/include/opkele/types.h b/include/opkele/types.h
index de44a5c..d5ad258 100644
--- a/include/opkele/types.h
+++ b/include/opkele/types.h
@@ -1,246 +1,207 @@
#ifndef __OPKELE_TYPES_H
#define __OPKELE_TYPES_H
/**
* @file
* @brief various types declarations
*/
#include <ostream>
#include <vector>
#include <string>
#include <map>
#include <set>
+#include <list>
+#include <opkele/iterator.h>
#include <opkele/tr1-mem.h>
namespace opkele {
using std::vector;
using std::string;
using std::map;
using std::ostream;
using std::multimap;
using std::set;
+ using std::list;
+ using std::iterator;
+ using std::forward_iterator_tag;
/**
* the OpenID operation mode
*/
typedef enum _mode_t {
mode_associate,
mode_checkid_immediate,
mode_checkid_setup,
mode_check_association
} mode_t;
/**
* the association secret container
*/
class secret_t : public vector<unsigned char> {
public:
/**
* xor the secret and hmac together and encode, using base64
* @param key_d pointer to the message digest
* @param rv reference to the return value
*/
void enxor_to_base64(const unsigned char *key_d,string& rv) const;
/**
* decode base64-encoded secret and xor it with the message digest
* @param key_d pointer to the message digest
* @param b64 base64-encoded secret value
*/
void enxor_from_base64(const unsigned char *key_d,const string& b64);
/**
* plainly encode to base64 representation
* @param rv reference to the return value
*/
void to_base64(string& rv) const;
/**
* decode cleartext secret from base64
* @param b64 base64-encoded representation of the secret value
*/
void from_base64(const string& b64);
};
/**
* Interface to the association.
*/
class association_t {
public:
virtual ~association_t() { }
/**
* retrieve the server with which association was established.
* @return server name
*/
virtual string server() const = 0;
/**
* retrieve the association handle.
* @return handle
*/
virtual string handle() const = 0;
/**
* retrieve the association type.
* @return association type
*/
virtual string assoc_type() const = 0;
/**
* retrieve the association secret.
* @return association secret
*/
virtual secret_t secret() const = 0;
/**
* retrieve the number of seconds the association expires in.
* @return seconds till expiration
*/
virtual int expires_in() const = 0;
/**
* check whether the association is stateless.
* @return true if stateless
*/
virtual bool stateless() const = 0;
/**
* check whether the association is expired.
* @return true if expired
*/
virtual bool is_expired() const = 0;
};
/**
* the shared_ptr<> for association_t object type
*/
typedef tr1mem::shared_ptr<association_t> assoc_t;
+ class basic_openid_message {
+ public:
+ typedef list<string> fields_t;
+ typedef util::forward_iterator_proxy<
+ string,const string&,const string*
+ > fields_iterator;
+
+ basic_openid_message() { }
+ basic_openid_message(const basic_openid_message& x);
+ void copy_to(basic_openid_message& x) const;
+
+ virtual bool has_field(const string& n) const = 0;
+ virtual const string& get_field(const string& n) const = 0;
+
+ virtual bool has_ns(const string& uri) const;
+ virtual string get_ns(const string& uri) const;
+
+ virtual fields_iterator fields_begin() const = 0;
+ virtual fields_iterator fields_end() const = 0;
+
+ virtual string append_query(const string& url) const;
+ virtual string query_string() const;
+
+
+ virtual void reset_fields();
+ virtual void set_field(const string& n,const string& v);
+ virtual void reset_field(const string& n);
+
+ virtual void from_keyvalues(const string& kv);
+
+ void add_to_signed(const string& fields);
+ string find_ns(const string& uri,const char *pfx) const;
+ string allocate_ns(const string& uri,const char *pfx);
+ };
+
+ class openid_message_t : public basic_openid_message, public map<string,string> {
+ public:
+ openid_message_t() { }
+ openid_message_t(const basic_openid_message& x)
+ : basic_openid_message(x) { }
+
+ void copy_to(basic_openid_message& x) const;
+
+ bool has_field(const string& n) const;
+ const string& get_field(const string& n) const;
+ virtual fields_iterator fields_begin() const;
+ virtual fields_iterator fields_end() const;
+
+ void reset_fields();
+ void set_field(const string& n,const string& v);
+ void reset_field(const string& n);
+ };
+
/**
* request/response parameters map
*/
- class params_t : public map<string,string> {
+ class params_t : public openid_message_t {
public:
/**
* check whether the parameter is present.
* @param n the parameter name
* @return true if yes
*/
- bool has_param(const string& n) const;
+ bool has_param(const string& n) const {
+ return has_field(n); }
/**
* retrieve the parameter (const version)
* @param n the parameter name
* @return the parameter value
* @throw failed_lookup if there is no such parameter
*/
- const string& get_param(const string& n) const;
- /**
- * retrieve the parameter.
- * @param n the parameter name
- * @return the parameter value
- * @throw failed_lookup if there is no such parameter
- */
- string& get_param(const string& n);
+ const string& get_param(const string& n) const {
+ return get_field(n); }
/**
* parse the OpenID key/value data.
* @param kv the OpenID key/value data
*/
- void parse_keyvalues(const string& kv);
- /**
- * sign the fields.
- * @param secret the secret used for signing
- * @param sig reference to the string, containing base64-encoded
- * result
- * @param slist the comma-separated list of fields to sign
- * @param prefix the string to prepend to parameter names
- */
- void sign(secret_t secret,string& sig,const string& slist,const char *prefix=0) const;
+ void parse_keyvalues(const string& kv) {
+ from_keyvalues(kv); }
- /**
- * append parameters to the URL as a GET-request parameters.
- * @param url the base URL
- * @param prefix the string to prepend to parameter names
- * @return the ready-to-use location
- */
- string append_query(const string& url,const char *prefix = "openid.") const;
+ string append_query(const string& url,const char *prefix="openid.") const;
- /**
- * make up a query string suitable for use in GET and POST
- * requests.
- * @param prefix string to prened to parameter names
- * @return query string
- */
- string query_string(const char *prefix = "openid.") const;
};
- /**
- * dump the key/value pairs for the parameters to the stream.
- * @param o output stream
- * @param p the parameters
- */
- ostream& operator << (ostream& o,const params_t& p);
-
- namespace xrd {
-
- struct priority_compare {
- inline bool operator()(long a,long b) const {
- return (a<0) ? false : (b<0) ? true : (a<b);
- }
- };
-
- template <typename _DT>
- class priority_map : public multimap<long,_DT,priority_compare> {
- typedef multimap<long,_DT,priority_compare> map_type;
- public:
-
- inline _DT& add(long priority,const _DT& d) {
- return insert(typename map_type::value_type(priority,d))->second;
- }
- };
-
- typedef priority_map<string> canonical_ids_t;
- typedef priority_map<string> local_ids_t;
- typedef set<string> types_t;
- typedef priority_map<string> uris_t;
-
- class service_t {
- public:
- types_t types;
- uris_t uris;
- local_ids_t local_ids;
- string provider_id;
-
- void clear() {
- types.clear();
- uris.clear(); local_ids.clear();
- provider_id.clear();
- }
- };
- typedef priority_map<service_t> services_t;
-
- class XRD_t {
- public:
- time_t expires;
-
- canonical_ids_t canonical_ids;
- local_ids_t local_ids;
- services_t services;
- string provider_id;
-
- void clear() {
- expires = 0;
- canonical_ids.clear(); local_ids.clear();
- services.clear();
- provider_id.clear();
- }
- bool empty() const {
- return
- canonical_ids.empty()
- && local_ids.empty()
- && services.empty();
- }
-
- };
-
- }
-
}
#endif /* __OPKELE_TYPES_H */
diff --git a/include/opkele/util.h b/include/opkele/util.h
index 085c9e6..e9176b0 100644
--- a/include/opkele/util.h
+++ b/include/opkele/util.h
@@ -1,143 +1,151 @@
#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);
/**
* 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);
+
+ 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);
+
}
}
#endif /* __OPKELE_UTIL_H */
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 989de28..c58ec3f 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,31 +1,34 @@
lib_LTLIBRARIES = libopkele.la
AM_CPPFLAGS = ${CPPFLAGS_DEBUG}
DEFAULT_INCLUDES = -I${top_builddir}
INCLUDES = \
- -I${top_srcdir}/include/ \
+ -I${top_builddir}/include/ -I${top_srcdir}/include/ \
${KONFORKA_CFLAGS} \
${OPENSSL_CFLAGS} \
${LIBCURL_CPPFLAGS} \
${PCRE_CFLAGS} ${EXPAT_CFLAGS} ${TIDY_CFLAGS}
libopkele_la_LIBADD = \
${LIBCURL} \
${PCRE_LIBS} ${EXPAT_LIBS} \
${OPENSSL_LIBS} \
${KONFORKA_LIBS} ${TIDY_LIBS}
libopkele_la_SOURCES = \
params.cc \
util.cc \
server.cc \
secret.cc \
data.cc \
consumer.cc \
exception.cc \
extension.cc \
sreg.cc \
extension_chain.cc \
curl.cc expat.cc \
- discovery.cc
+ discovery.cc \
+ basic_rp.cc \
+ prequeue_rp.cc \
+ openid_message.cc
libopkele_la_LDFLAGS = \
-version-info 2:0:0
diff --git a/lib/basic_rp.cc b/lib/basic_rp.cc
new file mode 100644
index 0000000..763a391
--- a/dev/null
+++ b/lib/basic_rp.cc
@@ -0,0 +1,311 @@
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <opkele/basic_rp.h>
+#include <opkele/exception.h>
+#include <opkele/uris.h>
+#include <opkele/data.h>
+#include <opkele/util.h>
+#include <opkele/curl.h>
+
+namespace opkele {
+
+ static void dh_get_secret(
+ secret_t& secret, const basic_openid_message& om,
+ const char *exp_assoc, const char *exp_sess,
+ util::dh_t& dh,
+ size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*) ) try {
+ if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess)
+ throw bad_input(OPKELE_CP_ "Unexpected associate response");
+ util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public"));
+ vector<unsigned char> ck(DH_size(dh)+1);
+ unsigned char *ckptr = &(ck.front())+1;
+ int cklen = DH_compute_key(ckptr,s_pub,dh);
+ if(cklen<0)
+ throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()");
+ if(cklen && (*ckptr)&0x80) {
+ (*(--ckptr))=0; ++cklen; }
+ unsigned char key_digest[d_len];
+ secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key"));
+ }catch(opkele::failed_lookup& ofl) {
+ throw bad_input(OPKELE_CP_ "Incoherent response from OP");
+ } OPKELE_RETHROW
+
+ static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) {
+ util::curl_pick_t curl = util::curl_pick_t::easy_init();
+ if(!curl)
+ throw exception_curl(OPKELE_CP_ "failed to initialize curl");
+ string request = inm.query_string();
+ CURLcode r;
+ (r=curl.misc_sets())
+ || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str()))
+ || (r=curl.easy_setopt(CURLOPT_POST,1))
+ || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data()))
+ || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length()))
+ || (r=curl.set_write());
+ if(r)
+ throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
+ if( (r=curl.easy_perform()) )
+ throw exception_curl(OPKELE_CP_ "failed to perform curly request",r);
+ oum.from_keyvalues(curl.response);
+ }
+
+
+ assoc_t basic_RP::associate(const string& OP) {
+ util::dh_t dh = DH_new();
+ if(!dh)
+ throw exception_openssl(OPKELE_CP_ "failed to DH_new()");
+ dh->p = util::dec_to_bignum(data::_default_p);
+ dh->g = util::dec_to_bignum(data::_default_g);
+ if(!DH_generate_key(dh))
+ throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()");
+ openid_message_t req;
+ req.set_field("ns",OIURI_OPENID20);
+ req.set_field("mode","associate");
+ req.set_field("dh_modulus",util::bignum_to_base64(dh->p));
+ req.set_field("dh_gen",util::bignum_to_base64(dh->g));
+ req.set_field("dh_consumer_public",util::bignum_to_base64(dh->pub_key));
+ openid_message_t res;
+ req.set_field("assoc_type","HMAC-SHA256");
+ req.set_field("session_type","DH-SHA256");
+ secret_t secret;
+ int expires_in;
+ try {
+ direct_request(res,req,OP);
+ dh_get_secret( secret, res,
+ "HMAC-SHA256", "DH-SHA256",
+ dh, SHA256_DIGEST_LENGTH, SHA256 );
+ expires_in = util::string_to_long(res.get_field("expires_in"));
+ }catch(exception& e) {
+ try {
+ req.set_field("assoc_type","HMAC-SHA1");
+ req.set_field("session_type","DH-SHA1");
+ direct_request(res,req,OP);
+ dh_get_secret( secret, res,
+ "HMAC-SHA1", "DH-SHA1",
+ dh, SHA_DIGEST_LENGTH, SHA1 );
+ expires_in = util::string_to_long(res.get_field("expires_in"));
+ }catch(bad_input& e) {
+ throw dumb_RP(OPKELE_CP_ "OP failed to supply an association");
+ }
+ }
+ return store_assoc(
+ OP, res.get_field("assoc_handle"),
+ res.get_field("assoc_type"), secret,
+ expires_in );
+ }
+
+ basic_openid_message& basic_RP::checkid_(
+ basic_openid_message& rv,
+ mode_t mode,
+ const string& return_to,const string& realm,
+ extension_t *ext) {
+ rv.reset_fields();
+ rv.set_field("ns",OIURI_OPENID20);
+ if(mode==mode_checkid_immediate)
+ rv.set_field("mode","checkid_immediate");
+ else if(mode==mode_checkid_setup)
+ rv.set_field("mode","checkid_setup");
+ else
+ throw bad_input(OPKELE_CP_ "unknown checkid_* mode");
+ if(realm.empty() && return_to.empty())
+ throw bad_input(OPKELE_CP_ "At least one of realm and return_to must be non-empty");
+ if(!realm.empty()) {
+ rv.set_field("realm",realm);
+ rv.set_field("trust_root",realm);
+ }
+ if(!return_to.empty())
+ rv.set_field("return_to",return_to);
+ const openid_endpoint_t& ep = get_endpoint();
+ rv.set_field("claimed_id",ep.claimed_id);
+ rv.set_field("identity",ep.local_id);
+ try {
+ rv.set_field("assoc_handle",find_assoc(ep.uri)->handle());
+ }catch(dumb_RP& drp) {
+ }catch(failed_lookup& fl) {
+ try {
+ rv.set_field("assoc_handle",associate(ep.uri)->handle());
+ }catch(dumb_RP& drp) { }
+ } OPKELE_RETHROW
+ if(ext) ext->checkid_hook(rv);
+ return rv;
+ }
+
+ class signed_part_message_proxy : public basic_openid_message {
+ public:
+ const basic_openid_message& x;
+ set<string> signeds;
+
+ signed_part_message_proxy(const basic_openid_message& xx) : x(xx) {
+ const string& slist = x.get_field("signed");
+ string::size_type p = 0;
+ while(true) {
+ string::size_type co = slist.find(',',p);
+ string f = (co==string::npos)
+ ?slist.substr(p):slist.substr(p,co-p);
+ signeds.insert(f);
+ if(co==string::npos) break;
+ p = co+1;
+ }
+ }
+
+ bool has_field(const string& n) const {
+ return signeds.find(n)!=signeds.end() && x.has_field(n); }
+ const string& get_field(const string& n) const {
+ if(signeds.find(n)==signeds.end())
+ throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed");
+ return x.get_field(n); }
+
+ fields_iterator fields_begin() const {
+ return signeds.begin(); }
+ fields_iterator fields_end() const {
+ return signeds.end(); }
+ };
+
+ static void parse_query(const string& u,string::size_type q,
+ map<string,string>& p) {
+ if(q==string::npos)
+ return;
+ assert(u[q]=='?');
+ ++q;
+ string::size_type l = u.size();
+ while(q<l) {
+ string::size_type eq = u.find('=',q);
+ string::size_type am = u.find('&',q);
+ if(am==string::npos) {
+ if(eq==string::npos) {
+ p[""] = u.substr(q);
+ }else{
+ p[u.substr(q,eq-q)] = u.substr(eq+1);
+ }
+ break;
+ }else{
+ if(eq==string::npos || eq>am) {
+ p[""] = u.substr(q,eq-q);
+ }else{
+ p[u.substr(q,eq-q)] = u.substr(eq+1,am-eq-1);
+ }
+ q = ++am;
+ }
+ }
+ }
+
+ void basic_RP::id_res(const basic_openid_message& om,extension_t *ext) {
+ bool o2 = om.has_field("ns")
+ && om.get_field("ns")==OIURI_OPENID20;
+ if( (!o2) && om.has_field("user_setup_url"))
+ throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided",
+ om.get_field("user_setup_url"));
+ string m = om.get_field("mode");
+ if(o2 && m=="setup_needed")
+ throw id_res_setup(OPKELE_CP_ "setup needed, no setup url provided");
+ if(m=="cancel")
+ throw id_res_cancel(OPKELE_CP_ "authentication cancelled");
+ bool go_dumb=false;
+ try {
+ string OP = o2
+ ?om.get_field("op_endpoint")
+ :get_endpoint().uri;
+ assoc_t assoc = retrieve_assoc(
+ OP,om.get_field("assoc_handle"));
+ if(om.get_field("sig")!=util::base64_signature(assoc,om))
+ throw id_res_mismatch(OPKELE_CP_ "signature mismatch");
+ }catch(dumb_RP& drp) {
+ go_dumb=true;
+ }catch(failed_lookup& e) {
+ go_dumb=true;
+ } OPKELE_RETHROW
+ if(go_dumb) {
+ try {
+ string OP = o2
+ ?om.get_field("op_endpoint")
+ :get_endpoint().uri;
+ check_authentication(OP,om);
+ }catch(failed_check_authentication& fca) {
+ throw id_res_failed(OPKELE_CP_ "failed to check_authentication()");
+ } OPKELE_RETHROW
+ }
+ signed_part_message_proxy signeds(om);
+ if(o2) {
+ check_nonce(om.get_field("op_endpoint"),
+ om.get_field("response_nonce"));
+ static const char *mustsign[] = {
+ "op_endpoint", "return_to", "response_nonce", "assoc_handle",
+ "claimed_id", "identity" };
+ for(int ms=0;ms<(sizeof(mustsign)/sizeof(*mustsign));++ms) {
+ if(om.has_field(mustsign[ms]) && !signeds.has_field(mustsign[ms]))
+ throw bad_input(OPKELE_CP_ string("Field '")+mustsign[ms]+"' is not signed against the specs");
+ }
+ if( (
+ (om.has_field("claimed_id")?1:0)
+ ^
+ (om.has_field("identity")?1:0)
+ )&1 )
+ throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent");
+
+ string turl = util::rfc_3986_normalize_uri(get_this_url());
+ util::strip_uri_fragment_part(turl);
+ string rurl = util::rfc_3986_normalize_uri(om.get_field("return_to"));
+ util::strip_uri_fragment_part(rurl);
+ string::size_type
+ tq = turl.find('?'), rq = rurl.find('?');
+ if(
+ ((tq==string::npos)?turl:turl.substr(0,tq))
+ !=
+ ((rq==string::npos)?rurl:rurl.substr(0,rq))
+ )
+ throw id_res_bad_return_to(OPKELE_CP_ "return_to url doesn't match request url");
+ map<string,string> tp; parse_query(turl,tq,tp);
+ map<string,string> rp; parse_query(rurl,rq,rp);
+ for(map<string,string>::const_iterator rpi=rp.begin();rpi!=rp.end();++rpi) {
+ map<string,string>::const_iterator tpi = tp.find(rpi->first);
+ if(tpi==tp.end())
+ throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to is missing from the request");
+ if(tpi->second!=rpi->second)
+ throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to doesn't matche the request");
+ }
+
+ if(om.has_field("claimed_id")) {
+ verify_OP(
+ om.get_field("op_endpoint"),
+ om.get_field("claimed_id"),
+ om.get_field("identity") );
+ }
+
+ }
+ if(ext) ext->id_res_hook(om,signeds);
+ }
+
+ class check_auth_message_proxy : public basic_openid_message {
+ public:
+ const basic_openid_message& x;
+
+ check_auth_message_proxy(const basic_openid_message& xx) : x(xx) { }
+
+ bool has_field(const string& n) const { return x.has_field(n); }
+ const string& get_field(const string& n) const {
+ static const string checkauthmode="check_authentication";
+ return (n=="mode")?checkauthmode:x.get_field(n); }
+ bool has_ns(const string& uri) const {return x.has_ns(uri); }
+ string get_ns(const string& uri) const { return x.get_ns(uri); }
+ fields_iterator fields_begin() const {
+ return x.fields_begin(); }
+ fields_iterator fields_end() const {
+ return x.fields_end(); }
+ };
+
+ void basic_RP::check_authentication(const string& OP,
+ const basic_openid_message& om){
+ openid_message_t res;
+ direct_request(res,check_auth_message_proxy(om),OP);
+ if(res.has_field("is_valid")) {
+ if(res.get_field("is_valid")=="true") {
+ if(res.has_field("invalidate_handle"))
+ invalidate_assoc(OP,res.get_field("invalidate_handle"));
+ return;
+ }
+ }
+ throw failed_check_authentication(
+ OPKELE_CP_ "failed to verify response");
+ }
+
+}
diff --git a/lib/consumer.cc b/lib/consumer.cc
index 3c3b4f8..ebda262 100644
--- a/lib/consumer.cc
+++ b/lib/consumer.cc
@@ -1,395 +1,395 @@
#include <algorithm>
#include <cassert>
#include <cstring>
#include <opkele/util.h>
#include <opkele/curl.h>
#include <opkele/exception.h>
#include <opkele/data.h>
#include <opkele/consumer.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <iostream>
#include "config.h"
#include <pcre.h>
namespace opkele {
using namespace std;
using util::curl_t;
using util::curl_pick_t;
class pcre_matches_t {
public:
int *_ov;
int _s;
pcre_matches_t() : _ov(0), _s(0) { }
pcre_matches_t(int s) : _ov(0), _s(s) {
if(_s&1) ++_s;
_s += _s>>1;
_ov = new int[_s];
}
~pcre_matches_t() throw() { if(_ov) delete[] _ov; }
int begin(int i) const { return _ov[i<<1]; }
int end(int i) const { return _ov[(i<<1)+1]; }
int length(int i) const { int t=i<<1; return _ov[t+1]-_ov[t]; }
};
class pcre_t {
public:
pcre *_p;
pcre_t() : _p(0) { }
pcre_t(pcre *p) : _p(p) { }
pcre_t(const char *re,int opts) : _p(0) {
static const char *errptr; static int erroffset;
_p = pcre_compile(re,opts,&errptr,&erroffset,NULL);
if(!_p)
throw internal_error(OPKELE_CP_ string("Failed to compile regexp: ")+errptr);
}
~pcre_t() throw() { if(_p) (*pcre_free)(_p); }
pcre_t& operator=(pcre *p) { if(_p) (*pcre_free)(_p); _p=p; return *this; }
operator const pcre*(void) const { return _p; }
operator pcre*(void) { return _p; }
int exec(const string& s,pcre_matches_t& m) {
if(!_p)
throw internal_error(OPKELE_CP_ "Trying to execute absent regexp");
return pcre_exec(_p,NULL,s.c_str(),s.length(),0,0,m._ov,m._s);
}
};
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_pick_t curl = curl_pick_t::easy_init();
if(!curl)
throw exception_curl(OPKELE_CP_ "failed to initialize curl");
CURLcode r;
(r=curl.misc_sets())
|| (r=curl.easy_setopt(CURLOPT_URL,server.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);
params_t p; p.parse_keyvalues(curl.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)+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_sha1[SHA_DIGEST_LENGTH];
SHA1(ckptr,cklen,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,extension_t *ext) {
return checkid_(mode_checkid_immediate,identity,return_to,trust_root,ext);
}
string consumer_t::checkid_setup(const string& identity,const string& return_to,const string& trust_root,extension_t *ext) {
return checkid_(mode_checkid_setup,identity,return_to,trust_root,ext);
}
string consumer_t::checkid_(mode_t mode,const string& identity,const string& return_to,const string& trust_root,extension_t *ext) {
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 = canonicalize(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 {
string ah = find_assoc(server)->handle();
p["assoc_handle"] = ah;
}catch(failed_lookup& fl) {
string ah = associate(server)->handle();
p["assoc_handle"] = ah;
}
- if(ext) ext->checkid_hook(p,identity);
+ if(ext) ext->checkid_hook(p);
return p.append_query(server);
}
void consumer_t::id_res(const params_t& pin,const string& identity,extension_t *ext) {
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"):canonicalize(identity),server,delegate);
params_t ps;
try {
assoc_t assoc = retrieve_assoc(server,pin.get_param("openid.assoc_handle"));
if(assoc->is_expired())
throw id_res_expired_on_delivery(OPKELE_CP_ "retrieve_assoc() has returned expired handle");
const string& sigenc = pin.get_param("openid.sig");
vector<unsigned char> sig;
util::decode_base64(sigenc,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(ext) ps[f.substr(sizeof("openid.")-1)] = pin.get_param(f);
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) {
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()");
}
}
- if(ext) ext->id_res_hook(pin,ps,identity);
+ if(ext) ext->id_res_hook(pin,ps);
}
void consumer_t::check_authentication(const string& server,const params_t& p) {
string request = "openid.mode=check_authentication";
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_pick_t curl = curl_pick_t::easy_init();
if(!curl)
throw exception_curl(OPKELE_CP_ "failed to initialize curl");
CURLcode r;
(r=curl.misc_sets())
|| (r=curl.easy_setopt(CURLOPT_URL,server.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);
params_t pp; pp.parse_keyvalues(curl.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_pick_t curl = curl_pick_t::easy_init();
if(!curl)
throw exception_curl(OPKELE_CP_ "failed to initialize curl");
string& html = curl.response;
CURLcode r;
(r=curl.misc_sets())
|| (r=curl.easy_setopt(CURLOPT_URL,url.c_str()))
|| (r=curl.set_write());
;
if(r)
throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
r = curl.easy_perform();
if(r && r!=CURLE_WRITE_ERROR)
throw exception_curl(OPKELE_CP_ "failed to perform curly request",r);
static const char *re_bre = "<\\s*body\\b", *re_hdre = "<\\s*head[^>]*>",
*re_lre = "<\\s*link\\b([^>]+)>",
*re_rre = "\\brel\\s*=\\s*['\"]([^'\"]+)['\"]",
*re_hre = "\\bhref\\s*=\\s*['\"]\\s*([^'\"\\s]+)\\s*['\"]";
pcre_matches_t m1(3), m2(3);
pcre_t bre(re_bre,PCRE_CASELESS);
if(bre.exec(html,m1)>0)
html.erase(m1.begin(0));
pcre_t hdre(re_hdre,PCRE_CASELESS);
if(hdre.exec(html,m1)<=0)
throw bad_input(OPKELE_CP_ "failed to find <head>");
html.erase(0,m1.end(0)+1);
pcre_t lre(re_lre,PCRE_CASELESS), rre(re_rre,PCRE_CASELESS), hre(re_hre,PCRE_CASELESS);
bool gotit = false;
while( (!gotit) && lre.exec(html,m1)>=2 ) {
static const char *whitespace = " \t";
string attrs(html,m1.begin(1),m1.length(1));
html.erase(0,m1.end(0)+1);
if(!( rre.exec(attrs,m1)>=2 && hre.exec(attrs,m2)>=2 ))
continue;
string rels(attrs,m1.begin(1),m1.length(1));
for(string::size_type ns = rels.find_first_not_of(whitespace);
ns!=string::npos;
ns=rels.find_first_not_of(whitespace,ns)) {
string::size_type s = rels.find_first_of(whitespace,ns);
string rel;
if(s==string::npos) {
rel.assign(rels,ns,string::npos);
ns=string::npos;
}else{
rel.assign(rels,ns,s-ns);
ns=s;
}
if(rel=="openid.server") {
server.assign(attrs,m2.begin(1),m2.length(1));
if(!delegate.empty()) {
gotit = true;
break;
}
}else if(rel=="openid.delegate") {
delegate.assign(attrs,m2.begin(1),m2.length(1));
if(!server.empty()) {
gotit = true;
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");
}
string consumer_t::normalize(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;
}
string::size_type qm = rv.find('?',i);
string::size_type sl = rv.find('/',i);
if(qm!=string::npos) {
if(sl==string::npos || sl>qm)
rv.insert(qm,1,'/');
}else{
if(sl==string::npos)
rv += '/';
}
return rv;
}
string consumer_t::canonicalize(const string& url) {
string rv = normalize(url);
curl_t curl = curl_t::easy_init();
if(!curl)
throw exception_curl(OPKELE_CP_ "failed to initialize curl()");
string html;
CURLcode r;
(r=curl.misc_sets())
|| (r=curl.easy_setopt(CURLOPT_URL,rv.c_str()))
|| (r=curl.easy_setopt(CURLOPT_NOBODY,1))
;
if(r)
throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
r = curl.easy_perform();
if(r)
throw exception_curl(OPKELE_CP_ "failed to perform curly request",r);
const char *eu = 0;
r = curl.easy_getinfo(CURLINFO_EFFECTIVE_URL,&eu);
if(r)
throw exception_curl(OPKELE_CP_ "failed to get CURLINFO_EFFECTIVE_URL",r);
rv = eu;
return normalize(rv);
}
}
diff --git a/lib/discovery.cc b/lib/discovery.cc
index d868308..93409f4 100644
--- a/lib/discovery.cc
+++ b/lib/discovery.cc
@@ -1,446 +1,537 @@
#include <list>
#include <opkele/curl.h>
#include <opkele/expat.h>
#include <opkele/uris.h>
#include <opkele/discovery.h>
#include <opkele/exception.h>
#include <opkele/util.h>
#include <opkele/tidy.h>
#include <opkele/debug.h>
#include "config.h"
#define XRDS_HEADER "X-XRDS-Location"
#define CT_HEADER "Content-Type"
namespace opkele {
using std::list;
using xrd::XRD_t;
using xrd::service_t;
+ /* TODO: the whole discovery thing needs cleanup and optimization due to
+ * many changes of concept. */
+
static const char *whitespace = " \t\r\n";
static const char *i_leaders = "=@+$!(";
static const size_t max_html = 16384;
+ static const struct service_type_t {
+ const char *uri;
+ const char *forceid;
+ } service_types[] = {
+ { STURI_OPENID20_OP, IDURI_SELECT20 },
+ { STURI_OPENID20, 0 },
+ { STURI_OPENID11, 0 },
+ { STURI_OPENID10, 0 }
+ };
+ enum {
+ st_index_1 = 2, st_index_2 = 1
+ };
+
+
static inline bool is_qelement(const XML_Char *n,const char *qen) {
return !strcasecmp(n,qen);
}
static inline bool is_element(const XML_Char *n,const char *en) {
if(!strcasecmp(n,en)) return true;
int nl = strlen(n), enl = strlen(en);
if( (nl>=(enl+1)) && n[nl-enl-1]=='\t'
&& !strcasecmp(&n[nl-enl],en) )
return true;
return false;
}
static long element_priority(const XML_Char **a) {
for(;*a;++a)
if(!strcasecmp(*(a++),"priority")) {
long rv;
return (sscanf(*a,"%ld",&rv)==1)?rv:-1;
}
return -1;
}
class idigger_t : public util::curl_t, public util::expat_t {
public:
string xri_proxy;
enum {
- xmode_html = 1, xmode_xrd = 2
+ xmode_html = 1, xmode_xrd = 2, xmode_cid = 4
};
int xmode;
string xrds_location;
string http_content_type;
service_t html_openid1;
service_t html_openid2;
string cdata_buf;
long status_code;
string status_string;
typedef list<string> pt_stack_t;
pt_stack_t pt_stack;
int skipping;
bool parser_choked;
string save_html;
XRD_t *xrd;
service_t *xrd_service;
string* cdata;
idigger_t()
: util::curl_t(easy_init()),
util::expat_t(0),
xri_proxy(XRI_PROXY_URL) {
CURLcode r;
(r=misc_sets())
|| (r=set_write())
|| (r=set_header())
;
if(r)
throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
}
~idigger_t() throw() { }
- void discover(idiscovery_t& result,const string& identity) {
- result.clear();
+ string discover(endpoint_discovery_iterator& oi,const string& identity) {
+ string rv;
+ idiscovery_t idis;
string::size_type fsc = identity.find_first_not_of(whitespace);
if(fsc==string::npos)
- throw bad_input(OPKELE_CP_ "whtiespace-only identity");
+ throw bad_input(OPKELE_CP_ "whitespace-only identity");
string::size_type lsc = identity.find_last_not_of(whitespace);
assert(lsc!=string::npos);
if(!strncasecmp(identity.c_str()+fsc,"xri://",sizeof("xri://")-1))
fsc += sizeof("xri://")-1;
if((fsc+1)>=lsc)
throw bad_input(OPKELE_CP_ "not a character of importance in identity");
string id(identity,fsc,lsc-fsc+1);
+ idis.clear();
if(strchr(i_leaders,id[0])) {
- result.normalized_id = id;
- result.xri_identity = true;
- /* TODO: further canonicalize xri identity? Like folding case or whatever... */
- discover_at(
- result,
- xri_proxy + util::url_encode(id)+
- "?_xrd_r=application/xrd+xml;sep=false", xmode_xrd);
- if(status_code!=100)
- throw failed_xri_resolution(OPKELE_CP_
- "XRI resolution failed with '"+status_string+"' message",status_code);
- if(result.xrd.canonical_ids.empty())
- throw opkele::failed_discovery(OPKELE_CP_ "No CanonicalID for XRI identity found");
- result.canonicalized_id = result.xrd.canonical_ids.begin()->second;
+ /* TODO: further normalize xri identity? Like folding case
+ * or whatever... */
+ rv = idis.normalized_id = id;
+ idis.xri_identity = true;
+ set<string> cids;
+ for(const struct service_type_t *st=service_types;
+ st<&service_types[sizeof(service_types)/sizeof(*service_types)];++st) {
+ idis.clear();
+ discover_at( idis,
+ xri_proxy + util::url_encode(id)+
+ "?_xrd_t="+util::url_encode(st->uri)+
+ "&_xrd_r=application/xrd%2Bxml"
+ ";sep=true;refs=true",
+ xmode_xrd );
+ if(status_code==241) continue;
+ if(status_code!=100)
+ throw failed_xri_resolution(OPKELE_CP_
+ "XRI resolution failed with '"+status_string+"' message"
+ ", while looking for SEP with type '"+st->uri+"'", status_code);
+ if(idis.xrd.canonical_ids.empty())
+ throw opkele::failed_discovery(OPKELE_CP_ "No CanonicalID found for XRI identity found");
+ string cid = idis.xrd.canonical_ids.begin()->second;
+ if(cids.find(cid)==cids.end()) {
+ cids.insert(cid);
+ idis.clear();
+ discover_at( idis,
+ xri_proxy + util::url_encode(id)+
+ "?_xrd_t="+util::url_encode(st->uri)+
+ "&_xrd_r=application/xrd%2Bxml"
+ ";sep=true;refs=true",
+ xmode_xrd );
+ if(status_code==241) continue;
+ if(status_code!=100)
+ throw failed_xri_resolution(OPKELE_CP_
+ "XRI resolution failed with '"+status_string+"' message"
+ ", while looking for SEP with type '"+st->uri+"'"
+ " on canonical id", status_code);
+ }
+ idis.canonicalized_id = cid;
+ queue_endpoints(oi,idis,st);
+ }
}else{
- result.xri_identity = false;
+ idis.xri_identity = false;
if(id.find("://")==string::npos)
id.insert(0,"http://");
string::size_type fp = id.find('#');
if(fp!=string::npos) {
string::size_type qp = id.find('?');
if(qp==string::npos || qp<fp)
id.erase(fp);
else if(qp>fp)
id.erase(fp,qp-fp);
}
- result.normalized_id = util::rfc_3986_normalize_uri(id);
- discover_at(result,id,xmode_html|xmode_xrd);
+ rv = idis.normalized_id = util::rfc_3986_normalize_uri(id);
+ discover_at(idis,id,xmode_html|xmode_xrd);
const char * eu = 0;
CURLcode r = easy_getinfo(CURLINFO_EFFECTIVE_URL,&eu);
if(r)
throw exception_curl(OPKELE_CP_ "failed to get CURLINFO_EFFECTIVE_URL",r);
- result.canonicalized_id = util::rfc_3986_normalize_uri(eu); /* XXX: strip fragment part? */
+ string cid = util::strip_uri_fragment_part( idis.canonicalized_id = util::rfc_3986_normalize_uri(eu) );
if(xrds_location.empty()) {
- html2xrd(result.xrd);
+ html2xrd(oi,idis);
}else{
- discover_at(result,xrds_location,xmode_xrd);
- if(result.xrd.empty())
- html2xrd(result.xrd);
+ idis.clear();
+ idis.canonicalized_id = cid;
+ discover_at(idis,xrds_location,xmode_xrd);
+ if(idis.xrd.empty())
+ html2xrd(oi,idis);
+ else{
+ for(const service_type_t *st=service_types;
+ st<&service_types[sizeof(service_types)/sizeof(*service_types)];++st)
+ queue_endpoints(oi,idis,st);
+ }
}
}
+ return rv;
}
- void discover_at(idiscovery_t& result,const string& url,int xm) {
+ void discover_at(idiscovery_t& idis,const string& url,int xm) {
+ DOUT_("Doing discovery at " << url);
CURLcode r = easy_setopt(CURLOPT_URL,url.c_str());
if(r)
throw exception_curl(OPKELE_CP_ "failed to set culry urlie",r);
http_content_type.clear();
xmode = xm;
prepare_to_parse();
if(xmode&xmode_html) {
xrds_location.clear();
save_html.clear();
save_html.reserve(max_html);
}
- xrd = &result.xrd;
+ xrd = &idis.xrd;
r = easy_perform();
if(r && r!=CURLE_WRITE_ERROR)
throw exception_curl(OPKELE_CP_ "failed to perform curly request",r);
if(!parser_choked) {
parse(0,0,true);
}else{
/* TODO: do not bother if we've seen xml */
try {
util::tidy_doc_t td = util::tidy_doc_t::create();
if(!td)
throw exception_tidy(OPKELE_CP_ "failed to create htmltidy document");
#ifndef NDEBUG
td.opt_set(TidyQuiet,false);
td.opt_set(TidyShowWarnings,false);
#endif /* NDEBUG */
td.opt_set(TidyForceOutput,true);
td.opt_set(TidyXhtmlOut,true);
td.opt_set(TidyDoctypeMode,TidyDoctypeOmit);
td.opt_set(TidyMark,false);
if(td.parse_string(save_html)<=0)
throw exception_tidy(OPKELE_CP_ "tidy failed to parse document");
if(td.clean_and_repair()<=0)
throw exception_tidy(OPKELE_CP_ "tidy failed to clean and repair");
util::tidy_buf_t tide;
if(td.save_buffer(tide)<=0)
throw exception_tidy(OPKELE_CP_ "tidy failed to save buffer");
prepare_to_parse();
parse(tide.c_str(),tide.size(),true);
}catch(exception_tidy& et) { }
}
save_html.clear();
}
void prepare_to_parse() {
(*(expat_t*)this) = parser_create_ns();
set_user_data(); set_element_handler();
set_character_data_handler();
if(xmode&xmode_html) {
html_openid1.clear(); html_openid2.clear();
parser_choked = false;
}
cdata = 0; xrd_service = 0; skipping = 0;
+ pt_stack.clear();
status_code = 100; status_string.clear();
}
- void html2xrd(XRD_t& x) {
- if(!html_openid1.uris.empty()) {
- html_openid1.types.insert(STURI_OPENID11);
- x.services.add(-1,html_openid1);
- }
+ void html2xrd(endpoint_discovery_iterator& oi,idiscovery_t& id) {
+ XRD_t& x = id.xrd;
if(!html_openid2.uris.empty()) {
html_openid2.types.insert(STURI_OPENID20);
x.services.add(-1,html_openid2);
+ queue_endpoints(oi,id,&service_types[st_index_2]);
+ }
+ if(!html_openid1.uris.empty()) {
+ html_openid1.types.insert(STURI_OPENID11);
+ x.services.add(-1,html_openid1);
+ queue_endpoints(oi,id,&service_types[st_index_1]);
}
}
size_t write(void *p,size_t s,size_t nm) {
/* TODO: limit total size */
size_t bytes = s*nm;
const char *inbuf = (const char*)p;
if(xmode&xmode_html) {
size_t mbts = save_html.capacity()-save_html.size();
size_t bts = 0;
if(mbts>0) {
bts = (bytes>mbts)?mbts:bytes;
save_html.append(inbuf,bts);
}
if(skipping<0) return bts;
}
if(skipping<0) return 0;
bool rp = parse(inbuf,bytes,false);
if(!rp) {
parser_choked = true;
skipping = -1;
if(!(xmode&xmode_html))
bytes = 0;
}
return bytes;
}
size_t header(void *p,size_t s,size_t nm) {
size_t bytes = s*nm;
const char *h = (const char*)p;
const char *colon = (const char*)memchr(p,':',bytes);
const char *space = (const char*)memchr(p,' ',bytes);
if(space && ( (!colon) || space<colon ) ) {
xrds_location.clear(); http_content_type.clear();
}else if(colon) {
const char *hv = ++colon;
int hnl = colon-h;
int rb;
for(rb = bytes-hnl-1;rb>0 && isspace(*hv);++hv,--rb);
while(rb>0 && isspace(hv[rb-1])) --rb;
if(rb) {
if( (hnl>=sizeof(XRDS_HEADER))
&& !strncasecmp(h,XRDS_HEADER":",
sizeof(XRDS_HEADER)) ) {
xrds_location.assign(hv,rb);
}else if( (hnl>=sizeof(CT_HEADER))
&& !strncasecmp(h,CT_HEADER":",
sizeof(CT_HEADER)) ) {
const char *sc = (const char*)memchr(
hv,';',rb);
http_content_type.assign(hv,sc?(sc-hv):rb);
}
}
}
return curl_t::header(p,s,nm);
}
void start_element(const XML_Char *n,const XML_Char **a) {
if(skipping<0) return;
if(skipping) {
if(xmode&xmode_html)
html_start_element(n,a);
++skipping; return;
}
if(pt_stack.empty()) {
if(is_qelement(n,NSURI_XRDS "\tXRDS"))
return;
if(is_qelement(n,NSURI_XRD "\tXRD")) {
assert(xrd);
xrd->clear();
pt_stack.push_back(n);
}else if(xmode&xmode_html) {
html_start_element(n,a);
}else{
skipping = -1;
}
}else{
int pt_s = pt_stack.size();
if(pt_s==1) {
if(is_qelement(n,NSURI_XRD "\tCanonicalID")) {
assert(xrd);
cdata = &(xrd->canonical_ids.add(element_priority(a),string()));
}else if(is_qelement(n,NSURI_XRD "\tLocalID")) {
assert(xrd);
cdata = &(xrd->local_ids.add(element_priority(a),string()));
}else if(is_qelement(n,NSURI_XRD "\tProviderID")) {
assert(xrd);
cdata = &(xrd->provider_id);
}else if(is_qelement(n,NSURI_XRD "\tService")) {
assert(xrd);
xrd_service = &(xrd->services.add(element_priority(a),
service_t()));
pt_stack.push_back(n);
}else if(is_qelement(n,NSURI_XRD "\tStatus")) {
for(;*a;) {
if(!strcasecmp(*(a++),"code")) {
if(sscanf(*(a++),"%ld",&status_code)==1 && status_code!=100) {
cdata = &status_string;
pt_stack.push_back(n);
break;
}
- }
+ }else
+ ++a;
}
}else if(is_qelement(n,NSURI_XRD "\tExpires")) {
assert(xrd);
cdata_buf.clear();
cdata = &cdata_buf;
}else if(xmode&xmode_html) {
html_start_element(n,a);
}else{
skipping = 1;
}
}else if(pt_s==2) {
if(is_qelement(pt_stack.back().c_str(), NSURI_XRD "\tService")) {
if(is_qelement(n,NSURI_XRD "\tType")) {
assert(xrd); assert(xrd_service);
cdata_buf.clear();
cdata = &cdata_buf;
}else if(is_qelement(n,NSURI_XRD "\tURI")) {
assert(xrd); assert(xrd_service);
cdata = &(xrd_service->uris.add(element_priority(a),string()));
}else if(is_qelement(n,NSURI_XRD "\tLocalID")
|| is_qelement(n,NSURI_OPENID10 "\tDelegate") ) {
assert(xrd); assert(xrd_service);
cdata = &(xrd_service->local_ids.add(element_priority(a),string()));
}else if(is_qelement(n,NSURI_XRD "\tProviderID")) {
assert(xrd); assert(xrd_service);
cdata = &(xrd_service->provider_id);
}else{
skipping = 1;
}
}else
skipping = 1;
}else if(xmode&xmode_html) {
html_start_element(n,a);
}else{
skipping = 1;
}
}
}
void end_element(const XML_Char *n) {
if(skipping<0) return;
if(skipping) {
--skipping; return;
}
if(is_qelement(n,NSURI_XRD "\tType")) {
assert(xrd); assert(xrd_service); assert(cdata==&cdata_buf);
xrd_service->types.insert(cdata_buf);
}else if(is_qelement(n,NSURI_XRD "\tService")) {
assert(xrd); assert(xrd_service);
assert(!pt_stack.empty());
assert(pt_stack.back()==(NSURI_XRD "\tService"));
pt_stack.pop_back();
xrd_service = 0;
}else if(is_qelement(n,NSURI_XRD "\tStatus")) {
assert(xrd);
if(is_qelement(pt_stack.back().c_str(),n)) {
assert(cdata==&status_string);
pt_stack.pop_back();
if(status_code!=100)
skipping = -1;
}
}else if(is_qelement(n,NSURI_XRD "\tExpires")) {
assert(xrd);
xrd->expires = util::w3c_to_time(cdata_buf);
}else if((xmode&xmode_html) && is_element(n,"head")) {
skipping = -1;
}
cdata = 0;
}
void character_data(const XML_Char *s,int l) {
if(skipping) return;
if(cdata) cdata->append(s,l);
}
void html_start_element(const XML_Char *n,const XML_Char **a) {
if(is_element(n,"meta")) {
bool heq = false;
string l;
for(;*a;a+=2) {
if(!( strcasecmp(a[0],"http-equiv")
|| strcasecmp(a[1],XRDS_HEADER) ))
heq = true;
else if(!strcasecmp(a[0],"content"))
l.assign(a[1]);
}
if(heq)
xrds_location = l;
}else if(is_element(n,"link")) {
string rels;
string href;
for(;*a;a+=2) {
if( !strcasecmp(a[0],"rel") ) {
rels.assign(a[1]);
}else if( !strcasecmp(a[0],"href") ) {
const char *ns = a[1];
for(;*ns && isspace(*ns);++ns);
href.assign(ns);
string::size_type lns=href.find_last_not_of(whitespace);
href.erase(lns+1);
}
}
for(string::size_type ns=rels.find_first_not_of(whitespace);
ns!=string::npos; ns=rels.find_first_not_of(whitespace,ns)) {
string::size_type s = rels.find_first_of(whitespace,ns);
string rel;
if(s==string::npos) {
rel.assign(rels,ns,string::npos);
ns = string::npos;
}else{
rel.assign(rels,ns,s-ns);
ns = s;
}
if(rel=="openid.server")
html_openid1.uris.add(-1,href);
else if(rel=="openid.delegate")
html_openid1.local_ids.add(-1,href);
else if(rel=="openid2.provider")
html_openid2.uris.add(-1,href);
else if(rel=="openid2.local_id")
html_openid2.local_ids.add(-1,href);
}
}else if(is_element(n,"body")) {
skipping = -1;
}
}
+ void queue_endpoints(endpoint_discovery_iterator& oi,
+ const idiscovery_t &id,
+ const service_type_t *st) {
+ openid_endpoint_t ep;
+ ep.claimed_id = id.canonicalized_id;
+ for(xrd::services_t::const_iterator isvc=id.xrd.services.begin();
+ isvc!=id.xrd.services.end(); ++isvc) {
+ const xrd::service_t svc = isvc->second;
+ if(svc.types.find(st->uri)==svc.types.end()) continue;
+ for(xrd::uris_t::const_iterator iu=svc.uris.begin();iu!=svc.uris.end();++iu) {
+ ep.uri = iu->second;
+ if(st->forceid) {
+ ep.local_id = ep.claimed_id = st->forceid;
+ *(oi++) = ep;
+ }else{
+ if(svc.local_ids.empty()) {
+ ep.local_id = ep.claimed_id;
+ *(oi++) = ep;
+ }else{
+ for(xrd::local_ids_t::const_iterator ilid=svc.local_ids.begin();
+ ilid!=svc.local_ids.end(); ++ilid) {
+ ep.local_id = ilid->second;
+ *(oi++) = ep;
+ }
+ }
+ }
+ }
+ }
+ }
+
};
- void idiscover(idiscovery_t& result,const string& identity) {
+ string idiscover(endpoint_discovery_iterator oi,const string& identity) {
idigger_t idigger;
- idigger.discover(result,identity);
+ return idigger.discover(oi,identity);
}
}
diff --git a/lib/extension.cc b/lib/extension.cc
index 8f22562..6451249 100644
--- a/lib/extension.cc
+++ b/lib/extension.cc
@@ -1,15 +1,15 @@
#include <opkele/exception.h>
#include <opkele/extension.h>
namespace opkele {
- void extension_t::checkid_hook(params_t& /* p */,const string& /* identity */ ) {
+ void extension_t::checkid_hook(basic_openid_message&) {
throw not_implemented(OPKELE_CP_ "Consumer checkid_hook not implemented");
}
- void extension_t::id_res_hook(const params_t& /* p */,const params_t& /* sp */,const string& /* identity */) {
+ void extension_t::id_res_hook(const basic_openid_message&,const basic_openid_message&) {
throw not_implemented(OPKELE_CP_ "Consumer id_res_hook not implemented");
}
- void extension_t::checkid_hook(const params_t& /* pin */,params_t& /* pout */) {
+ void extension_t::checkid_hook(const basic_openid_message&,basic_openid_message&) {
throw not_implemented(OPKELE_CP_ "Server checkid_hook not implemented");
}
}
diff --git a/lib/extension_chain.cc b/lib/extension_chain.cc
index 16537dc..5c2afd9 100644
--- a/lib/extension_chain.cc
+++ b/lib/extension_chain.cc
@@ -1,16 +1,16 @@
#include <cstdarg>
#include <opkele/extension_chain.h>
namespace opkele {
- void extension_chain_t::checkid_hook(params_t& p,const string& identity) {
- for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(p,identity);
+ void extension_chain_t::checkid_hook(basic_openid_message& om){
+ for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(om);
}
- void extension_chain_t::id_res_hook(const params_t& p,const params_t& sp,const string& identity) {
- for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(p,sp,identity);
+ void extension_chain_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) {
+ for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(om,sp);
}
- void extension_chain_t::checkid_hook(const params_t& pin,params_t& pout) {
- for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(pin,pout);
+ void extension_chain_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) {
+ for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(inm,oum);
}
}
diff --git a/lib/openid_message.cc b/lib/openid_message.cc
new file mode 100644
index 0000000..3b08748
--- a/dev/null
+++ b/lib/openid_message.cc
@@ -0,0 +1,228 @@
+#include <cassert>
+#include <opkele/types.h>
+#include <opkele/exception.h>
+#include <opkele/util.h>
+#include <opkele/debug.h>
+
+#include "config.h"
+
+namespace opkele {
+ using std::input_iterator_tag;
+ using std::unary_function;
+
+ struct __om_copier : public unary_function<const string&,void> {
+ public:
+ const basic_openid_message& from;
+ basic_openid_message& to;
+
+ __om_copier(basic_openid_message& to,const basic_openid_message& from)
+ : from(from), to(to) {
+ to.reset_fields();
+ }
+
+ result_type operator()(argument_type f) {
+ to.set_field(f,from.get_field(f)); }
+ };
+
+ basic_openid_message::basic_openid_message(const basic_openid_message& x) {
+ x.copy_to(*this);
+ }
+ void basic_openid_message::copy_to(basic_openid_message& x) const {
+ for_each(fields_begin(),fields_end(),
+ __om_copier(x,*this) );
+ }
+
+ struct __om_ns_finder : public unary_function<const string&,bool> {
+ public:
+ const basic_openid_message& om;
+ const string& uri;
+
+ __om_ns_finder(const basic_openid_message& om,
+ const string& uri) : om(om), uri(uri) { }
+
+ result_type operator()(argument_type f) {
+ return
+ (!strncmp(f.c_str(),"ns.",sizeof("ns.")-1))
+ && om.get_field(f)==uri ;
+ }
+ };
+
+ bool basic_openid_message::has_ns(const string& uri) const {
+ fields_iterator ei = fields_end();
+ fields_iterator i = find_if(fields_begin(),fields_end(),
+ __om_ns_finder(*this,uri));
+ return !(i==ei);
+ }
+ string basic_openid_message::get_ns(const string& uri) const {
+ fields_iterator ei = fields_end();
+ fields_iterator i = find_if(fields_begin(),fields_end(),
+ __om_ns_finder(*this,uri));
+ if(i==ei)
+ throw failed_lookup(OPKELE_CP_ string("failed to find namespace ")+uri);
+ return i->substr(3);
+ }
+
+ struct __om_query_builder : public unary_function<const string&,void> {
+ public:
+ const basic_openid_message& om;
+ string& rv;
+ bool first;
+
+ __om_query_builder(string& rv,const basic_openid_message& om)
+ : om(om), first(true), rv(rv) {
+ for_each(om.fields_begin(),om.fields_end(),*this);
+ }
+ __om_query_builder(string& rv,const basic_openid_message& om,const string& url)
+ : om(om), first(true), rv(rv) {
+ rv = url;
+ if(rv.find('?')==string::npos)
+ rv += '?';
+ else
+ first = false;
+ for_each(om.fields_begin(),om.fields_end(),*this);
+ }
+
+ result_type operator()(argument_type f) {
+ if(first)
+ first = false;
+ else
+ rv += '&';
+ rv += "openid."; rv+= f;
+ rv += '=';
+ rv += util::url_encode(om.get_field(f));
+ }
+ };
+
+ string basic_openid_message::append_query(const string& url) const {
+ string rv;
+ return __om_query_builder(rv,*this,url).rv;
+ }
+ string basic_openid_message::query_string() const {
+ string rv;
+ return __om_query_builder(rv,*this).rv;
+ }
+
+ void basic_openid_message::reset_fields() {
+ throw not_implemented(OPKELE_CP_ "reset_fields() not implemented");
+ }
+ void basic_openid_message::set_field(const string& n,const string& v) {
+ throw not_implemented(OPKELE_CP_ "set_field() not implemented");
+ }
+ void basic_openid_message::reset_field(const string& n) {
+ throw not_implemented(OPKELE_CP_ "reset_field() not implemented");
+ }
+
+ void basic_openid_message::from_keyvalues(const string& kv) {
+ reset_fields();
+ string::size_type p = 0;
+ while(true) {
+ string::size_type co = kv.find(':',p);
+ if(co==string::npos)
+ break;
+#ifndef POSTELS_LAW
+ string::size_type nl = kv.find('\n',co+1);
+ if(nl==string::npos)
+ throw bad_input(OPKELE_CP_ "malformed input");
+ if(nl>co)
+ insert(value_type(kv.substr(p,co-p),kv.substr(co+1,nl-co-1)));
+ p = nl+1;
+#else /* POSTELS_LAW */
+ string::size_type lb = kv.find_first_of("\r\n",co+1);
+ if(lb==string::npos) {
+ set_field(kv.substr(p,co-p),kv.substr(co+1));
+ break;
+ }
+ if(lb>co)
+ set_field(kv.substr(p,co-p),kv.substr(co+1,lb-co-1));
+ string::size_type nolb = kv.find_first_not_of("\r\n",lb);
+ if(nolb==string::npos)
+ break;
+ p = nolb;
+#endif /* POSTELS_LAW */
+ }
+ }
+
+ void basic_openid_message::add_to_signed(const string& fields) {
+ string::size_type fnc = fields.find_first_not_of(",");
+ if(fnc==string::npos)
+ throw bad_input(OPKELE_CP_ "Trying to add nothing in particular to the list of signed fields");
+ string signeds;
+ try {
+ signeds = get_field("signed");
+ string::size_type lnc = signeds.find_last_not_of(",");
+ if(lnc==string::npos)
+ signeds.assign(fields,fnc,fields.size()-fnc);
+ else{
+ string::size_type ss = signeds.size();
+ if(lnc==(ss-1)) {
+ signeds+= ',';
+ signeds.append(fields,fnc,fields.size()-fnc);
+ }else{
+ if(lnc<(ss-2))
+ signeds.replace(lnc+2,ss-lnc-2,
+ fields,fnc,fields.size()-fnc);
+ else
+ signeds.append(fields,fnc,fields.size()-fnc);
+ }
+ }
+ }catch(failed_lookup&) {
+ signeds.assign(fields,fnc,fields.size()-fnc);
+ }
+ set_field("signed",signeds);
+ }
+
+ string basic_openid_message::find_ns(const string& uri,const char *pfx) const {
+ if(has_field("ns"))
+ return get_ns(uri);
+ return pfx;
+ }
+ string basic_openid_message::allocate_ns(const string& uri,const char *pfx) {
+ if(!has_field("ns"))
+ return pfx;
+ if(has_ns(uri))
+ throw bad_input(OPKELE_CP_ "OpenID message already contains namespace");
+ string rv = pfx;
+ if(has_field("ns."+rv)) {
+ string::reference c=rv[rv.length()];
+ for(c='a';c<='z' && has_field("ns."+rv);++c);
+ if(c=='z')
+ throw exception(OPKELE_CP_ "Failed to allocate namespace");
+ }
+ set_field("ns."+rv,uri);
+ return rv;
+ }
+
+ void openid_message_t::copy_to(basic_openid_message& x) const {
+ x.reset_fields();
+ for(const_iterator i=begin();i!=end();++i)
+ x.set_field(i->first,i->second);
+ }
+
+ bool openid_message_t::has_field(const string& n) const {
+ return find(n)!=end();
+ }
+ const string& openid_message_t::get_field(const string& n) const {
+ const_iterator i=find(n);
+ if(i==end())
+ throw failed_lookup(OPKELE_CP_ n+": no such field");
+ return i->second;
+ }
+
+ openid_message_t::fields_iterator openid_message_t::fields_begin() const {
+ return util::map_keys_iterator<const_iterator,string,const string&,const string*>(begin(),end());
+ }
+ openid_message_t::fields_iterator openid_message_t::fields_end() const {
+ return util::map_keys_iterator<const_iterator,string,const string&,const string*>(end(),end());
+ }
+
+ void openid_message_t::reset_fields() {
+ clear();
+ }
+ void openid_message_t::set_field(const string& n,const string& v) {
+ insert(value_type(n,v));
+ }
+ void openid_message_t::reset_field(const string& n) {
+ erase(n);
+ }
+
+}
diff --git a/lib/params.cc b/lib/params.cc
index 7a572c1..6805516 100644
--- a/lib/params.cc
+++ b/lib/params.cc
@@ -1,121 +1,30 @@
#include <opkele/types.h>
#include <opkele/exception.h>
#include <opkele/util.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include "config.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;
-#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
@@ -1,171 +1,171 @@
#include <cstring>
#include <vector>
#include <openssl/sha.h>
#include <openssl/hmac.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)+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;
}
SHA1(ckptr,cklen,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,extension_t *ext) {
checkid_(mode_checkid_immediate,pin,return_to,pout,ext);
}
void server_t::checkid_setup(const params_t& pin,string& return_to,params_t& pout,extension_t *ext) {
checkid_(mode_checkid_setup,pin,return_to,pout,ext);
}
void server_t::checkid_(mode_t mode,const params_t& pin,string& return_to,params_t& pout,extension_t *ext) {
if(mode!=mode_checkid_immediate && mode!=mode_checkid_setup)
throw bad_input(OPKELE_CP_ "invalid checkid_* mode");
pout.clear();
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);
if(pin.has_param("openid.assoc_handle"))
pout["invalidate_handle"]=pin.get_param("openid.assoc_handle");
}
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["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["signed"]="mode,identity,return_to";
if(ext) ext->checkid_hook(pin,pout);
- pout.sign(assoc->secret(),pout["sig"],pout["signed"]);
+ pout["sig"] = util::base64_signature(assoc,pout);
}
void server_t::check_authentication(const params_t& pin,params_t& pout) {
vector<unsigned char> sig;
const string& sigenc = pin.get_param("openid.sig");
util::decode_base64(sigenc,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 tmp = retrieve_assoc(h);
}catch(invalid_handle& ih) {
pout["invalidate_handle"] = h;
}catch(failed_lookup& fl) { }
}
}
}
diff --git a/lib/sreg.cc b/lib/sreg.cc
index 03edf57..7e2d588 100644
--- a/lib/sreg.cc
+++ b/lib/sreg.cc
@@ -1,126 +1,140 @@
#include <opkele/exception.h>
#include <opkele/sreg.h>
#include <opkele/uris.h>
#include <algorithm>
namespace opkele {
using std::find;
static const struct _sreg_field {
const char *fieldname;
sreg_t::fieldbit_t fieldbit;
} fields[] = {
{ "nickname", sreg_t::field_nickname },
{ "email", sreg_t::field_email },
{ "fullname", sreg_t::field_fullname },
{ "dob", sreg_t::field_dob },
{ "gender", sreg_t::field_gender },
{ "postcode", sreg_t::field_postcode },
{ "country", sreg_t::field_country },
{ "language", sreg_t::field_language },
{ "timezone", sreg_t::field_timezone }
};
# define fields_BEGIN fields
# define fields_END &fields[sizeof(fields)/sizeof(*fields)]
typedef const struct _sreg_field *fields_iterator;
bool operator==(const struct _sreg_field& fd,const string& fn) {
return fd.fieldname==fn;
}
- void sreg_t::checkid_hook(params_t& p,const string& /* identity */) {
+ void sreg_t::checkid_hook(basic_openid_message& om) {
string fr, fo;
for(fields_iterator f=fields_BEGIN;f<fields_END;++f) {
if(f->fieldbit&fields_required) {
if(!fr.empty()) fr+=",";
fr += f->fieldname;
}
if(f->fieldbit&fields_optional) {
if(!fo.empty()) fo+=",";
fo += f->fieldname;
}
}
- p["ns.sreg"] = OIURI_SREG11;
- if(!fr.empty()) p["sreg.required"]=fr;
- if(!fo.empty()) p["sreg.optional"]=fo;
- if(!policy_url.empty()) p["sreg.policy_url"]=policy_url;
+ string pfx = om.allocate_ns(OIURI_SREG11,"sreg");
+ if(!fr.empty()) om.set_field(pfx+".required",fr);
+ if(!fo.empty()) om.set_field(pfx+".optional",fo);
+ if(!policy_url.empty()) om.set_field(pfx+".policy_url",policy_url);
}
- void sreg_t::id_res_hook(const params_t& /* p */,const params_t& sp,const string& /* identity */) {
+ void sreg_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) {
clear();
+ string pfx;
+ try {
+ pfx = om.find_ns(OIURI_SREG11,"sreg");
+ }catch(failed_lookup& fl) {
+ try {
+ pfx = om.find_ns(OIURI_SREG10,"sreg");
+ }catch(failed_lookup& fl) {
+ return;
+ }
+ }
+ pfx += '.';
for(fields_iterator f=fields_BEGIN;f<fields_END;++f) {
- string fn = "sreg."; fn+=f->fieldname;
- if(!sp.has_param(fn)) continue;
+ string fn = pfx; fn+=f->fieldname;
+ if(!sp.has_field(fn)) continue;
has_fields |= f->fieldbit;
- response[f->fieldbit]=sp.get_param(fn);
+ response[f->fieldbit]=sp.get_field(fn);
}
}
const string& sreg_t::get_field(fieldbit_t fb) const {
response_t::const_iterator i = response.find(fb);
if(i==response.end())
throw failed_lookup(OPKELE_CP_ "no field data available");
return i->second;
}
void sreg_t::set_field(fieldbit_t fb,const string& fv) {
response[fb] = fv;
has_fields |= fb;
}
void sreg_t::reset_field(fieldbit_t fb) {
has_fields &= ~fb;
response.erase(fb);
}
void sreg_t::clear() {
has_fields = 0; response.clear();
}
static long fields_list_to_bitmask(string& fl) {
long rv = 0;
while(!fl.empty()) {
string::size_type co = fl.find(',');
string fn;
if(co==string::npos) {
fn = fl; fl.erase();
}else{
fn = fl.substr(0,co); fl.erase(0,co+1);
}
fields_iterator f = find(fields_BEGIN,fields_END,fn);
if(f!=fields_END)
rv |= f->fieldbit;
}
return rv;
}
- void sreg_t::checkid_hook(const params_t& pin,params_t& pout) {
+ void sreg_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) {
+ string ins = inm.find_ns(OIURI_SREG11,"sreg");
fields_optional = 0; fields_required = 0; policy_url.erase();
fields_response = 0;
try {
- string fl = pin.get_param("openid.sreg.required");
+ string fl = inm.get_field(ins+".required");
fields_required = fields_list_to_bitmask(fl);
}catch(failed_lookup&) { }
try {
- string fl = pin.get_param("openid.sreg.optional");
+ string fl = inm.get_field(ins+".optional");
fields_optional = fields_list_to_bitmask(fl);
}catch(failed_lookup&) { }
try {
- policy_url = pin.get_param("openid.sreg.policy_url");
+ policy_url = inm.get_field(ins+".policy_url");
}catch(failed_lookup&) { }
- setup_response(pin,pout);
+ setup_response(inm,oum);
+ string ons = oum.allocate_ns(OIURI_SREG11,"sreg");
fields_response &= has_fields;
+ string signeds = "ns."+ons;
for(fields_iterator f=fields_BEGIN;f<fields_END;++f) {
if(!(f->fieldbit&fields_response)) continue;
- if(!pout["signed"].empty())
- pout["signed"] +=',';
- string pn = "sreg."; pn += f->fieldname;
- pout["signed"] += pn;
- pout[pn] = get_field(f->fieldbit);
+ signeds +=',';
+ string pn = ons; pn += '.'; pn += f->fieldname;
+ signeds += pn;
+ oum.set_field(pn,get_field(f->fieldbit));
}
+ oum.add_to_signed(signeds);
}
- void sreg_t::setup_response(const params_t& /* pin */,params_t& /* pout */) {
+ void sreg_t::setup_response(const basic_openid_message& /* inm */,basic_openid_message& /* oum */) {
fields_response = (fields_required|fields_optional)&has_fields;
}
}
diff --git a/lib/util.cc b/lib/util.cc
index a9b9bed..54d6535 100644
--- a/lib/util.cc
+++ b/lib/util.cc
@@ -1,316 +1,383 @@
#include <errno.h>
#include <cassert>
#include <cctype>
#include <cstring>
#include <vector>
#include <string>
#include <stack>
#include <openssl/bio.h>
#include <openssl/evp.h>
+#include <openssl/hmac.h>
#include <curl/curl.h>
#include "opkele/util.h"
#include "opkele/exception.h"
+#include <config.h>
+#ifdef HAVE_DEMANGLE
+# include <cxxabi.h>
+#endif
+
namespace opkele {
using namespace std;
namespace util {
/*
* 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 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
+ /* 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;
+ }
+
+ 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);
+ }
+
}
}