summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--include/Makefile.am1
-rw-r--r--include/opkele/exception.h27
-rw-r--r--lib/Makefile.am3
-rw-r--r--lib/basic_op.cc320
4 files changed, 350 insertions, 1 deletions
diff --git a/include/Makefile.am b/include/Makefile.am
index 50fcb62..37fb961 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,32 +1,33 @@
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/uris.h \
opkele/tr1-mem.h \
opkele/basic_rp.h \
opkele/prequeue_rp.h \
opkele/iterator.h \
+ opkele/basic_op.h \
${NODIST_HEADERS_}
noinst_HEADERS = \
opkele/data.h \
opkele/curl.h opkele/expat.h opkele/tidy.h \
opkele/util.h \
opkele/debug.h \
opkele/discovery.h
dist-hook:
rm -f $(addprefix ${distdir}/,${NODIST_HEADERS_})
diff --git a/include/opkele/exception.h b/include/opkele/exception.h
index ccb39d9..5c8418e 100644
--- a/include/opkele/exception.h
+++ b/include/opkele/exception.h
@@ -285,51 +285,78 @@ namespace opkele {
/**
* 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) { }
};
+ /**
+ * thrown while processing OpenID request in OP. Signifies invalid realm
+ */
+ class bad_realm : public exception {
+ public:
+ bad_realm(OPKELE_E_PARS)
+ : exception(OPKELE_E_CONS) { }
+ };
+
+ /**
+ * thrown when attempting to retrieve return_to of one-way request
+ */
+ class no_return_to : public exception {
+ public:
+ no_return_to(OPKELE_E_PARS)
+ : exception(OPKELE_E_CONS) { }
+ };
+
+ /**
+ * thrown when querying identity of non-identity related request
+ */
+ class non_identity : public exception {
+ public:
+ non_identity(OPKELE_E_PARS)
+ : exception(OPKELE_E_CONS) { }
+ };
+
}
#endif /* __OPKELE_EXCEPTION_H */
diff --git a/lib/Makefile.am b/lib/Makefile.am
index c58ec3f..ac312d1 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,34 +1,35 @@
lib_LTLIBRARIES = libopkele.la
AM_CPPFLAGS = ${CPPFLAGS_DEBUG}
DEFAULT_INCLUDES = -I${top_builddir}
INCLUDES = \
-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 \
basic_rp.cc \
prequeue_rp.cc \
- openid_message.cc
+ openid_message.cc \
+ basic_op.cc
libopkele_la_LDFLAGS = \
-version-info 2:0:0
diff --git a/lib/basic_op.cc b/lib/basic_op.cc
new file mode 100644
index 0000000..22012bc
--- a/dev/null
+++ b/lib/basic_op.cc
@@ -0,0 +1,320 @@
+#include <time.h>
+#include <cassert>
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <opkele/data.h>
+#include <opkele/basic_op.h>
+#include <opkele/exception.h>
+#include <opkele/util.h>
+#include <opkele/uris.h>
+
+namespace opkele {
+
+ void basic_op::reset_vars() {
+ assoc.reset();
+ return_to.clear(); realm.clear();
+ claimed_id.clear(); identity.clear();
+ invalidate_handle.clear();
+ }
+
+ bool basic_op::has_return_to() const {
+ return !return_to.empty();
+ }
+ const string& basic_op::get_return_to() const {
+ if(return_to.empty())
+ throw no_return_to(OPKELE_CP_ "No return_to URL provided with request");
+ return return_to;
+ }
+
+ const string& basic_op::get_realm() const {
+ assert(!realm.empty());
+ return realm;
+ }
+
+ bool basic_op::has_identity() const {
+ return !identity.empty();
+ }
+ const string& basic_op::get_claimed_id() const {
+ if(claimed_id.empty())
+ throw non_identity(OPKELE_CP_ "attempting to retrieve claimed_id of non-identity related request");
+ assert(!identity.empty());
+ return claimed_id;
+ }
+ const string& basic_op::get_identity() const {
+ if(identity.empty())
+ throw non_identity(OPKELE_CP_ "attempting to retrieve identity of non-identity related request");
+ assert(!claimed_id.empty());
+ return identity;
+ }
+
+ bool basic_op::is_id_select() const {
+ return identity==IDURI_SELECT20;
+ }
+
+ void basic_op::select_identity(const string& c,const string& i) {
+ claimed_id = c; identity = i;
+ }
+ void basic_op::set_claimed_id(const string& c) {
+ claimed_id = c;
+ }
+
+ basic_openid_message& basic_op::associate(
+ basic_openid_message& oum,
+ const basic_openid_message& inm) try {
+ assert(inm.get_field("mode")=="associate");
+ util::dh_t dh;
+ util::bignum_t c_pub;
+ unsigned char key_digest[SHA256_DIGEST_LENGTH];
+ size_t d_len = 0;
+ enum {
+ sess_cleartext, sess_dh_sha1, sess_dh_sha256
+ } st = sess_cleartext;
+ string sts = inm.get_field("session_type");
+ string ats = inm.get_field("assoc_type");
+ if(sts=="DH-SHA1" || sts=="DH-SHA256") {
+ if(!(dh = DH_new()))
+ throw exception_openssl(OPKELE_CP_ "failed to DH_new()");
+ c_pub = util::base64_to_bignum(inm.get_field("dh_consumer_public"));
+ try { dh->p = util::base64_to_bignum(inm.get_field("dh_modulus"));
+ }catch(failed_lookup&) {
+ dh->p = util::dec_to_bignum(data::_default_p); }
+ try { dh->g = util::base64_to_bignum(inm.get_field("dh_gen"));
+ }catch(failed_lookup&) {
+ dh->g = util::dec_to_bignum(data::_default_g); }
+ if(!DH_generate_key(dh))
+ throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()");
+ vector<unsigned char> ck(DH_size(dh)+1);
+ unsigned char *ckptr = &(ck.front())+1;
+ int cklen = DH_compute_key(ckptr,c_pub,dh);
+ if(cklen<0)
+ throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()");
+ if(cklen && (*ckptr)&0x80) {
+ (*(--ckptr)) = 0; ++cklen; }
+ if(sts=="DH-SHA1") {
+ SHA1(ckptr,cklen,key_digest); d_len = SHA_DIGEST_LENGTH;
+ }else if(sts=="DH-SHA256") {
+ SHA256(ckptr,cklen,key_digest); d_len = SHA256_DIGEST_LENGTH;
+ }else
+ throw internal_error(OPKELE_CP_ "I thought I knew the session type");
+ }else
+ throw unsupported(OPKELE_CP_ "Unsupported session_type");
+ assoc_t assoc;
+ if(ats=="HMAC-SHA1")
+ assoc = alloc_assoc(ats,SHA_DIGEST_LENGTH,true);
+ else if(ats=="HMAC-SHA256")
+ assoc = alloc_assoc(ats,SHA256_DIGEST_LENGTH,true);
+ else
+ throw unsupported(OPKELE_CP_ "Unsupported assoc_type");
+ oum.reset_fields();
+ oum.set_field("ns",OIURI_OPENID20);
+ oum.set_field("assoc_type",assoc->assoc_type());
+ oum.set_field("assoc_handle",assoc->handle());
+ oum.set_field("expires_in",util::long_to_string(assoc->expires_in()));
+ secret_t secret = assoc->secret();
+ if(sts=="DH-SHA1" || sts=="DH-SHA256") {
+ if(d_len != secret.size())
+ throw bad_input(OPKELE_CP_ "Association secret and session MAC are not of the same size");
+ oum.set_field("session_type",sts);
+ oum.set_field("dh_server_public",util::bignum_to_base64(dh->pub_key));
+ string b64; secret.enxor_to_base64(key_digest,b64);
+ oum.set_field("enc_mac_key",b64);
+ }else /* TODO: support cleartext over encrypted connection */
+ throw unsupported(OPKELE_CP_ "Unsupported session type");
+ return oum;
+ } catch(unsupported& u) {
+ oum.reset_fields();
+ oum.set_field("ns",OIURI_OPENID20);
+ oum.set_field("error",u.what());
+ oum.set_field("error_code","unsupported-type");
+ oum.set_field("session_type","DH-SHA256");
+ oum.set_field("assoc_type","HMAC-SHA256");
+ return oum;
+ }
+
+ void basic_op::checkid_(const basic_openid_message& inm,
+ extension_t *ext) {
+ reset_vars();
+ string mode = inm.get_field("mode");
+ if(mode=="checkid_setup")
+ mode = mode_checkid_setup;
+ else if(mode=="checkid_immediate")
+ mode = mode_checkid_immediate;
+ else
+ throw bad_input(OPKELE_CP_ "Invalid checkid_* mode");
+ try {
+ assoc = retrieve_assoc(invalidate_handle=inm.get_field("assoc_handle"));
+ invalidate_handle.clear();
+ }catch(failed_lookup&) {
+ // no handle specified or no valid assoc found, go dumb
+ assoc = alloc_assoc("HMAC-SHA256",SHA256_DIGEST_LENGTH,true);
+ }
+ try {
+ openid2 = (inm.get_field("ns")==OIURI_OPENID20);
+ }catch(failed_lookup&) { openid2 = false; }
+ try {
+ return_to = inm.get_field("return_to");
+ }catch(failed_lookup&) { }
+ if(openid2) {
+ try {
+ realm = inm.get_field("realm");
+ }catch(failed_lookup&) {
+ try {
+ realm = inm.get_field("trust_root");
+ }catch(failed_lookup&) {
+ if(return_to.empty())
+ throw bad_input(OPKELE_CP_
+ "Both realm and return_to are unset");
+ realm = return_to;
+ }
+ }
+ }else{
+ try {
+ realm = inm.get_field("trust_root");
+ }catch(failed_lookup&) {
+ if(return_to.empty())
+ throw bad_input(OPKELE_CP_
+ "Both realm and return_to are unset");
+ realm = return_to;
+ }
+ }
+ try {
+ identity = inm.get_field("identity");
+ try {
+ claimed_id = inm.get_field("claimed_id");
+ }catch(failed_lookup&) {
+ if(openid2)
+ throw bad_input(OPKELE_CP_
+ "claimed_id and identity must be either both present or both absent");
+ }
+ }catch(failed_lookup&) {
+ if(openid2 && inm.has_field("claimed_id"))
+ throw bad_input(OPKELE_CP_
+ "claimed_id and identity must be either both present or both absent");
+ }
+ verify_return_to();
+ }
+
+ basic_openid_message& basic_op::id_res(basic_openid_message& om) {
+ assert(assoc);
+ assert(!return_to.empty());
+ assert(!is_id_select());
+ time_t now = time(0);
+ struct tm gmt; gmtime_r(&now,&gmt);
+ char w3timestr[24];
+ if(!strftime(w3timestr,sizeof(w3timestr),"%Y-%m-%dT%H:%M:%SZ",&gmt))
+ throw failed_conversion(OPKELE_CP_
+ "Failed to build time string for nonce" );
+ om.set_field("ns",OIURI_OPENID20);
+ om.set_field("mode","id_res");
+ om.set_field("op_endpoint",get_op_endpoint());
+ string ats = "ns,mode,op_endpoint,return_to,response_nonce,"
+ "assoc_handle,signed";
+ if(!identity.empty()) {
+ om.set_field("identity",identity);
+ om.set_field("claimed_id",claimed_id);
+ ats += ",identity,claimed_id";
+ }
+ om.set_field("return_to",return_to);
+ string nonce = w3timestr;
+ om.set_field("response_nonce",alloc_nonce(nonce,assoc->stateless()));
+ if(!invalidate_handle.empty()) {
+ om.set_field("invalidate_handle",invalidate_handle);
+ ats += ",invalidate_handle";
+ }
+ om.set_field("assoc_handle",assoc->handle());
+ om.add_to_signed(ats);
+ om.set_field("sig",util::base64_signature(assoc,om));
+ return om;
+ }
+
+ basic_openid_message& basic_op::cancel(basic_openid_message& om) {
+ assert(!return_to.empty());
+ om.set_field("ns",OIURI_OPENID20);
+ om.set_field("mode","cancel");
+ return om;
+ }
+
+ basic_openid_message& basic_op::error(basic_openid_message& om,
+ const string& error,const string& contact,
+ const string& reference ) {
+ assert(!return_to.empty());
+ om.set_field("ns",OIURI_OPENID20);
+ om.set_field("mode","error");
+ om.set_field("error",error);
+ om.set_field("contact",contact);
+ om.set_field("reference",reference);
+ return om;
+ }
+
+ basic_openid_message& basic_op::setup_needed(
+ basic_openid_message& oum,const basic_openid_message& inm) {
+ assert(mode==mode_checkid_immediate);
+ assert(!return_to.empty());
+ if(openid2) {
+ oum.set_field("ns",OIURI_OPENID20);
+ oum.set_field("mode","setup_needed");
+ }else{
+ oum.set_field("mode","id_res");
+ static const string setupmode = "checkid_setup";
+ oum.set_field("user_setup_url",
+ util::change_mode_message_proxy(inm,setupmode)
+ .append_query(get_op_endpoint()));
+ }
+ return oum;
+ }
+
+ basic_openid_message& basic_op::check_authentication(
+ basic_openid_message& oum,
+ const basic_openid_message& inm) try {
+ assert(inm.get_field("mode")=="check_authentication");
+ oum.reset_fields();
+ oum.set_field("ns",OIURI_OPENID20);
+ bool o2;
+ try {
+ o2 = (inm.get_field("ns")==OIURI_OPENID20);
+ }catch(failed_lookup&) { o2 = false; }
+ string nonce;
+ if(o2) {
+ try {
+ if(!check_nonce(nonce = inm.get_field("response_nonce")))
+ throw failed_check_authentication(OPKELE_CP_ "Invalid nonce");
+ }catch(failed_lookup&) {
+ throw failed_check_authentication(OPKELE_CP_ "No nonce provided with check_authentication request");
+ }
+ }
+ try {
+ assoc = retrieve_assoc(inm.get_field("assoc_handle"));
+ if(!assoc->stateless())
+ throw failed_check_authentication(OPKELE_CP_ "Will not do check_authentication on a stateful handle");
+ }catch(failed_lookup&) {
+ throw failed_check_authentication(OPKELE_CP_ "No assoc_handle or invalid assoc_handle specified with check_authentication request");
+ }
+ static const string idresmode = "id_res";
+ try {
+ if(util::base64_signature(assoc,util::change_mode_message_proxy(inm,idresmode))!=inm.get_field("sig"))
+ throw failed_check_authentication(OPKELE_CP_ "Signature mismatch");
+ }catch(failed_lookup&) {
+ throw failed_check_authentication(OPKELE_CP_ "failed to calculate signature");
+ }
+ oum.set_field("is_valid","true");
+ try {
+ string h = inm.get_field("invalidate_handle");
+ try {
+ assoc_t ih = retrieve_assoc(h);
+ }catch(invalid_handle& ih) {
+ oum.set_field("invalidate_handle",h);
+ }catch(failed_lookup& ih) {
+ oum.set_field("invalidate_handle",h);
+ }
+ }catch(failed_lookup&) { }
+ if(o2) {
+ assert(!nonce.empty());
+ invalidate_nonce(nonce);
+ }
+ return oum;
+ }catch(failed_check_authentication& ) {
+ oum.set_field("is_valid","false");
+ return oum;
+ }
+
+}