summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--include/opkele/basic_op.h119
-rw-r--r--include/opkele/verify_op.h2
-rw-r--r--lib/basic_op.cc38
-rw-r--r--lib/verify_op.cc2
-rw-r--r--test/OP.cc2
5 files changed, 134 insertions, 29 deletions
diff --git a/include/opkele/basic_op.h b/include/opkele/basic_op.h
index 5bba1bf..4daed02 100644
--- a/include/opkele/basic_op.h
+++ b/include/opkele/basic_op.h
@@ -1,69 +1,174 @@
#ifndef __OPKELE_BASIC_OP_H
#define __OPKELE_BASIC_OP_H
#include <string>
#include <opkele/types.h>
#include <opkele/extension.h>
namespace opkele {
using std::string;
- class basic_op {
+ class basic_OP {
public:
mode_t mode;
assoc_t assoc;
bool openid2;
string return_to;
string realm;
string claimed_id;
string identity;
string invalidate_handle;
void reset_vars();
bool has_return_to() const;
const string& get_return_to() const;
const string& get_realm() const;
bool has_identity() const;
const string& get_claimed_id() const;
const string& get_identity() const;
bool is_id_select() const;
void select_identity(const string& c,const string& i);
void set_claimed_id(const string& c);
+ /** @name OpenID operations
+ * @{
+ */
+ /**
+ * Establish association with RP
+ * @param oum reply message
+ * @param inm request message
+ */
basic_openid_message& associate(
basic_openid_message& oum,
const basic_openid_message& inm);
+ /**
+ * Parse the checkid_* request. The function parses input message,
+ * retrieves the information needed for further processing,
+ * verifies what can be verified at this stage.
+ * @param inm incoming OpenID message
+ * @param ext extension/chain of extensions supported
+ */
void checkid_(const basic_openid_message& inm,extension_t *ext=0);
+ /**
+ * Build and sign a positive assertion message
+ * @param om outpu OpenID message
+ * @param ext extension/chain of extensions supported
+ * @return reference to om
+ */
basic_openid_message& id_res(basic_openid_message& om,
extension_t *ext=0);
+ /**
+ * Build a 'cancel' negative assertion
+ * @param om output OpenID message
+ * @return reference to om
+ */
basic_openid_message& cancel(basic_openid_message& om);
+ /**
+ * Build an 'error' reply
+ * @param om output OpenID message
+ * @param error a human-readable message indicating the cause
+ * @param contact contact address for the server administrator (can be empty)
+ * @param reference a reference token (can be empty)
+ * @return reference to om
+ */
basic_openid_message& error(basic_openid_message& om,
const string& error,const string& contact,
const string& reference );
+ /**
+ * Build a setup_needed reply to checkid_immediate request
+ * @param oum output OpenID message
+ * @param inm incoming OpenID request being processed
+ * @return reference to oum
+ */
basic_openid_message& setup_needed(
basic_openid_message& oum,const basic_openid_message& inm);
+ /**
+ * Process check_authentication request
+ * @param oum output OpenID message
+ * @param inm incoming request
+ * @return reference to oum
+ */
basic_openid_message& check_authentication(
basic_openid_message& oum,const basic_openid_message& inm);
-
+ /**
+ * @}
+ */
+
+ /**
+ * Verify return_to url. The default implementation checks whether
+ * return_to URI matches the realm
+ * @throw bad_realm in case of invalid realm
+ * @throw bad_return_to if return_to doesn't match the realm
+ * @see verify_op::verify_return_to()
+ */
virtual void verify_return_to();
- virtual assoc_t alloc_assoc(const string& t,size_t kl,bool sl) = 0;
- virtual assoc_t retrieve_assoc(const string& h) = 0;
-
- virtual string& alloc_nonce(string& nonce,bool sl) = 0;
+ /**
+ * @name Global persistent store API
+ * These functions are related to the associations with RPs storage
+ * and retrieval and nonce management.
+ * @{
+ */
+ /**
+ * Allocate association.
+ * @param type association type
+ * @param kl association key length
+ * @param sl true if the association is stateless
+ * @return association object
+ */
+ virtual assoc_t alloc_assoc(const string& type,size_t kl,bool sl) = 0;
+ /**
+ * Retrieve valid unexpired association
+ * @param handle association handle
+ * @return association object
+ */
+ virtual assoc_t retrieve_assoc(const string& handle) = 0;
+ /**
+ * Allocate nonce.
+ * @param nonce input-output parameter containing timestamp part of
+ * the nonce on input
+ * @param sl true if the nonce is
+ * @return reference to nonce
+ * @throw failed_lookup if no such valid unexpired association
+ * could be retrieved
+ */
+ virtual string& alloc_nonce(string& nonce) = 0;
+ /**
+ * Check nonce validity
+ * @param nonce nonce to check
+ * @return true if nonce found and isn't yet invalidated
+ */
virtual bool check_nonce(const string& nonce) = 0;
+ /**
+ * Invalidate nonce
+ * @param nonce nonce to check
+ */
virtual void invalidate_nonce(const string& nonce) = 0;
-
+ /**
+ * @}
+ */
+
+ /**
+ * @name Site particulars API
+ * @{
+ */
+ /**
+ * Query the absolute URL of the op endpoint
+ * @return fully qualified url of the OP endpoint
+ */
virtual const string get_op_endpoint() const = 0;
+ /**
+ * @}
+ */
};
}
#endif /* __OPKELE_BASIC_OP_H */
diff --git a/include/opkele/verify_op.h b/include/opkele/verify_op.h
index f5c97b2..6c3c386 100644
--- a/include/opkele/verify_op.h
+++ b/include/opkele/verify_op.h
@@ -1,16 +1,16 @@
#ifndef __OPKELE_VERIFY_OP_H
#define __OPKELE_VERIFY_OP_H
#include <opkele/basic_op.h>
namespace opkele {
- class verify_op : public basic_op {
+ class verify_op : public basic_OP {
public:
void verify_return_to();
};
}
#endif /* __OPKELE_VERIFY_OP_H */
diff --git a/lib/basic_op.cc b/lib/basic_op.cc
index 7a2dbd2..18446dc 100644
--- a/lib/basic_op.cc
+++ b/lib/basic_op.cc
@@ -1,85 +1,85 @@
#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() {
+ 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 {
+ bool basic_OP::has_return_to() const {
return !return_to.empty();
}
- const string& basic_op::get_return_to() const {
+ 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 {
+ const string& basic_OP::get_realm() const {
assert(!realm.empty());
return realm;
}
- bool basic_op::has_identity() const {
+ bool basic_OP::has_identity() const {
return !identity.empty();
}
- const string& basic_op::get_claimed_id() const {
+ 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 {
+ 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 {
+ bool basic_OP::is_id_select() const {
return identity==IDURI_SELECT20;
}
- void basic_op::select_identity(const string& c,const string& i) {
+ 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) {
+ void basic_OP::set_claimed_id(const string& c) {
claimed_id = c;
}
- basic_openid_message& basic_op::associate(
+ 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()");
@@ -110,49 +110,49 @@ namespace opkele {
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,
+ 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&) { }
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 {
@@ -172,122 +172,122 @@ namespace opkele {
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");
claimed_id = identity;
}
}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();
if(ext) ext->op_checkid_hook(inm);
}
- basic_openid_message& basic_op::id_res(basic_openid_message& om,
+ basic_openid_message& basic_OP::id_res(basic_openid_message& om,
extension_t *ext) {
assert(!return_to.empty());
assert(!is_id_select());
if(!assoc) {
assoc = alloc_assoc("HMAC-SHA256",SHA256_DIGEST_LENGTH,true);
}
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()));
+ om.set_field("response_nonce",alloc_nonce(nonce));
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);
if(ext) ext->op_id_res_hook(om);
om.set_field("sig",util::base64_signature(assoc,om));
return om;
}
- basic_openid_message& basic_op::cancel(basic_openid_message& 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,
+ 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& 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& 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");
@@ -299,32 +299,32 @@ namespace opkele {
}catch(failed_lookup&) {
throw failed_check_authentication(OPKELE_CP_ "failed to calculate signature");
}
oum.set_field("is_valid","true");
try {
string h = inm.get_field("invalidate_handle");
try {
assoc_t ih = retrieve_assoc(h);
}catch(invalid_handle& ih) {
oum.set_field("invalidate_handle",h);
}catch(failed_lookup& ih) {
oum.set_field("invalidate_handle",h);
}
}catch(failed_lookup&) { }
if(o2) {
assert(!nonce.empty());
invalidate_nonce(nonce);
}
return oum;
}catch(failed_check_authentication& ) {
oum.set_field("is_valid","false");
return oum;
}
- void basic_op::verify_return_to() {
+ void basic_OP::verify_return_to() {
if(realm.find('#')!=string::npos)
throw opkele::bad_realm(OPKELE_CP_ "authentication realm contains URI fragment");
if(!util::uri_matches_realm(return_to,realm))
throw bad_return_to(OPKELE_CP_ "return_to URL doesn't match realm");
}
}
diff --git a/lib/verify_op.cc b/lib/verify_op.cc
index e7c26b5..0beca2d 100644
--- a/lib/verify_op.cc
+++ b/lib/verify_op.cc
@@ -12,42 +12,42 @@ namespace opkele {
__RP_verifier_good_input(OPKELE_E_PARS)
: exception(OPKELE_E_CONS) { }
};
class RP_verifier : public iterator<output_iterator_tag,openid_endpoint_t,void> {
public:
int seen;
const string& return_to;
RP_verifier(const string& rt)
: return_to(rt), seen(0) { }
RP_verifier& operator*() { return *this; }
RP_verifier& operator=(const openid_endpoint_t& oep) {
if(util::uri_matches_realm(return_to,oep.uri))
throw __RP_verifier_good_input(OPKELE_CP_ "Found matching realm");
return *this;
}
RP_verifier& operator++() { ++seen; return *this; }
RP_verifier& operator++(int) { +seen; return *this; }
};
void verify_op::verify_return_to() {
- basic_op::verify_return_to();
+ basic_OP::verify_return_to();
try {
RP_verifier rpv(return_to);
string drealm = realm;
string::size_type csss = drealm.find("://*.");
if(csss==4 || csss==5)
drealm.replace(csss+3,1,"www");
const char *rtt[] = { STURI_OPENID20_RT, 0 };
yadiscover(rpv,drealm,rtt,false);
if(rpv.seen)
throw bad_return_to(OPKELE_CP_ "return_to URL doesn't match any found while doing discovery on RP");
}catch(__RP_verifier_good_input&) {
}catch(bad_return_to& brt) {
throw;
}catch(exception_network&) { }
}
}
diff --git a/test/OP.cc b/test/OP.cc
index c919d7f..ce54d92 100644
--- a/test/OP.cc
+++ b/test/OP.cc
@@ -140,49 +140,49 @@ class example_op_t : public opkele::verify_op {
S = sqlite3_mprintf(
"SELECT"
" a_handle,a_type,a_secret,a_stateless,"
" strftime('%%s',a_etime) AS a_etime,"
" a_itime"
" FROM assoc"
" WHERE a_handle=%Q AND a_itime IS NULL"
" AND datetime('now') < a_etime"
" LIMIT 1",
h.c_str() );
sqlite3_table_t T;
int nr,nc;
db.get_table(S,T,&nr,&nc);
if(nr<1)
throw opkele::failed_lookup(OPKELE_CP_
"couldn't retrieve valid unexpired assoc");
assert(nr==1); assert(nc==6);
opkele::secret_t secret; opkele::util::decode_base64(T.get(1,2,nc),secret);
return opkele::assoc_t(new opkele::association(
"", h, T.get(1,1,nc), secret,
strtol(T.get(1,4,nc),0,0),
strtol(T.get(1,3,nc),0,0) ));
}
- string& alloc_nonce(string& nonce,bool stateless) {
+ string& alloc_nonce(string& nonce) {
uuid_t uuid; uuid_generate(uuid);
nonce += opkele::util::encode_base64(uuid,sizeof(uuid));
sqlite3_mem_t<char*>
S = sqlite3_mprintf(
"INSERT INTO nonces"
" (n_once) VALUES (%Q)",
nonce.c_str() );
db.exec(S);
return nonce;
}
bool check_nonce(const string& nonce) {
sqlite3_mem_t<char*>
S = sqlite3_mprintf(
"SELECT 1"
" FROM nonces"
" WHERE n_once=%Q AND n_itime IS NULL",
nonce.c_str());
sqlite3_table_t T;
int nr,nc;
db.get_table(S,T,&nr,&nc);
return nr>=1;
}
void invalidate_nonce(const string& nonce) {
sqlite3_mem_t<char*>