summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--include/opkele/basic_rp.h36
-rw-r--r--lib/basic_rp.cc29
2 files changed, 63 insertions, 2 deletions
diff --git a/include/opkele/basic_rp.h b/include/opkele/basic_rp.h
index d5356aa..d096e0a 100644
--- a/include/opkele/basic_rp.h
+++ b/include/opkele/basic_rp.h
@@ -1,63 +1,99 @@
1#ifndef __OPKELE_BASIC_RP_H 1#ifndef __OPKELE_BASIC_RP_H
2#define __OPKELE_BASIC_RP_H 2#define __OPKELE_BASIC_RP_H
3 3
4#include <string> 4#include <string>
5#include <opkele/types.h> 5#include <opkele/types.h>
6#include <opkele/extension.h> 6#include <opkele/extension.h>
7 7
8namespace opkele { 8namespace opkele {
9 using std::string; 9 using std::string;
10 10
11 class basic_RP { 11 class basic_RP {
12 public: 12 public:
13 /**
14 * Claimed identifier from a parsed id_res message.
15 */
16 string claimed_id;
17 /**
18 * OP-Local identifier from a parsed id_res message.
19 */
20 string identity;
13 21
14 virtual ~basic_RP() { } 22 virtual ~basic_RP() { }
15 23
24 void reset_vars();
25
26 /**
27 * @name Assertion information retrieval
28 * Retrieval of the information passed with openid message
29 * @{
30 */
31 /**
32 * Find out if the assertion is about identity
33 * @return true if so
34 */
35 bool has_identity() const;
36 /**
37 * Get claimed identifier supplied with the request
38 * @return claimed identifier
39 * @throw non_identity if request is not about identity
40 */
41 const string& get_claimed_id() const;
42 /**
43 * Get the identity (OP-Local identifier) confirmed
44 * @return identity
45 * @throw non_identity if request is not about identity
46 */
47 const string& get_identity() const;
48 /**
49 * @}
50 */
51
16 /** 52 /**
17 * @name Global persistent store API 53 * @name Global persistent store API
18 * These are functions related to the associations with OP storage 54 * These are functions related to the associations with OP storage
19 * and retrieval and nonce records. They provide an interface to 55 * and retrieval and nonce records. They provide an interface to
20 * the persistent storage which is shared by all sessions. If the 56 * the persistent storage which is shared by all sessions. If the
21 * implementor prefers the dumb mode instead, the function should 57 * implementor prefers the dumb mode instead, the function should
22 * throw dumb_RP exception instead. 58 * throw dumb_RP exception instead.
23 * @see opkele::dumb_RP 59 * @see opkele::dumb_RP
24 * @{ 60 * @{
25 */ 61 */
26 /** 62 /**
27 * Store association and return allocated association object. 63 * Store association and return allocated association object.
28 * @param OP OP endpoint 64 * @param OP OP endpoint
29 * @param handle association handle 65 * @param handle association handle
30 * @param type association type 66 * @param type association type
31 * @param secret association secret 67 * @param secret association secret
32 * @params expires_in the number of seconds association expires in 68 * @params expires_in the number of seconds association expires in
33 * @return the association object 69 * @return the association object
34 * @throw dumb_RP for dumb RP 70 * @throw dumb_RP for dumb RP
35 */ 71 */
36 virtual assoc_t store_assoc( 72 virtual assoc_t store_assoc(
37 const string& OP,const string& handle, 73 const string& OP,const string& handle,
38 const string& type,const secret_t& secret, 74 const string& type,const secret_t& secret,
39 int expires_in) = 0; 75 int expires_in) = 0;
40 /** 76 /**
41 * Find valid unexpired association with an OP. 77 * Find valid unexpired association with an OP.
42 * @param OP OP endpoint URL 78 * @param OP OP endpoint URL
43 * @return association found 79 * @return association found
44 * @throw failed_lookup if no association found 80 * @throw failed_lookup if no association found
45 * @throw dumb_RP for dumb RP 81 * @throw dumb_RP for dumb RP
46 */ 82 */
47 virtual assoc_t find_assoc( 83 virtual assoc_t find_assoc(
48 const string& OP) = 0; 84 const string& OP) = 0;
49 /** 85 /**
50 * Retrieve valid association handle for an OP by handle. 86 * Retrieve valid association handle for an OP by handle.
51 * @param OP OP endpoint URL 87 * @param OP OP endpoint URL
52 * @param handle association handle 88 * @param handle association handle
53 * @return association found 89 * @return association found
54 * @throw failed_lookup if no association found 90 * @throw failed_lookup if no association found
55 * @throw dumb_RP for dumb RP 91 * @throw dumb_RP for dumb RP
56 */ 92 */
57 virtual assoc_t retrieve_assoc( 93 virtual assoc_t retrieve_assoc(
58 const string& OP,const string& handle) = 0; 94 const string& OP,const string& handle) = 0;
59 /** 95 /**
60 * Invalidate association with OP 96 * Invalidate association with OP
61 * @param OP OP endpoint URL 97 * @param OP OP endpoint URL
62 * @param handle association handle 98 * @param handle association handle
63 * @throw dumb_RP for dumb RP 99 * @throw dumb_RP for dumb RP
diff --git a/lib/basic_rp.cc b/lib/basic_rp.cc
index e65d9fb..3357d0b 100644
--- a/lib/basic_rp.cc
+++ b/lib/basic_rp.cc
@@ -1,61 +1,80 @@
1#include <cassert> 1#include <cassert>
2#include <openssl/sha.h> 2#include <openssl/sha.h>
3#include <openssl/hmac.h> 3#include <openssl/hmac.h>
4#include <opkele/basic_rp.h> 4#include <opkele/basic_rp.h>
5#include <opkele/exception.h> 5#include <opkele/exception.h>
6#include <opkele/uris.h> 6#include <opkele/uris.h>
7#include <opkele/data.h> 7#include <opkele/data.h>
8#include <opkele/util.h> 8#include <opkele/util.h>
9#include <opkele/util-internal.h> 9#include <opkele/util-internal.h>
10#include <opkele/curl.h> 10#include <opkele/curl.h>
11#include <opkele/debug.h>
11 12
12namespace opkele { 13namespace opkele {
13 14
15 void basic_RP::reset_vars() {
16 claimed_id.clear(); identity.clear();
17 }
18
19 const string& basic_RP::get_claimed_id() const {
20 if(claimed_id.empty())
21 throw non_identity(OPKELE_CP_ "attempting to retreive claimed_id of non-identity assertion");
22 assert(!identity.empty());
23 return claimed_id;
24 }
25
26 const string& basic_RP::get_identity() const {
27 if(identity.empty())
28 throw non_identity(OPKELE_CP_ "attempting to retrieve identity of non-identity related assertion");
29 assert(!claimed_id.empty());
30 return identity;
31 }
32
14 static void dh_get_secret( 33 static void dh_get_secret(
15 secret_t& secret, const basic_openid_message& om, 34 secret_t& secret, const basic_openid_message& om,
16 const char *exp_assoc, const char *exp_sess, 35 const char *exp_assoc, const char *exp_sess,
17 util::dh_t& dh, 36 util::dh_t& dh,
18 size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*), 37 size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*),
19 size_t exp_s_len) try { 38 size_t exp_s_len) try {
20 if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess) 39 if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess)
21 throw bad_input(OPKELE_CP_ "Unexpected associate response"); 40 throw bad_input(OPKELE_CP_ "Unexpected associate response");
22 util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public")); 41 util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public"));
23 vector<unsigned char> ck(DH_size(dh)+1); 42 vector<unsigned char> ck(DH_size(dh)+1);
24 unsigned char *ckptr = &(ck.front())+1; 43 unsigned char *ckptr = &(ck.front())+1;
25 int cklen = DH_compute_key(ckptr,s_pub,dh); 44 int cklen = DH_compute_key(ckptr,s_pub,dh);
26 if(cklen<0) 45 if(cklen<0)
27 throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); 46 throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()");
28 if(cklen && (*ckptr)&0x80) { 47 if(cklen && (*ckptr)&0x80) {
29 (*(--ckptr))=0; ++cklen; } 48 (*(--ckptr))=0; ++cklen; }
30 assert(d_len<=SHA256_DIGEST_LENGTH); 49 assert(d_len<=SHA256_DIGEST_LENGTH);
31 unsigned char key_digest[SHA256_DIGEST_LENGTH]; 50 unsigned char key_digest[SHA256_DIGEST_LENGTH];
32 secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key")); 51 secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key"));
33 if(secret.size()!=exp_s_len) 52 if(secret.size()!=exp_s_len)
34 throw bad_input(OPKELE_CP_ "Secret length isn't consistent with association type"); 53 throw bad_input(OPKELE_CP_ "Secret length isn't consistent with association type");
35 }catch(opkele::failed_lookup& ofl) { 54 }catch(opkele::failed_lookup& ofl) {
36 throw bad_input(OPKELE_CP_ "Incoherent response from OP"); 55 throw bad_input(OPKELE_CP_ "Incoherent response from OP");
37 } OPKELE_RETHROW 56 } OPKELE_RETHROW
38 57
39 static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) { 58 static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) {
40 util::curl_pick_t curl = util::curl_pick_t::easy_init(); 59 util::curl_pick_t curl = util::curl_pick_t::easy_init();
41 if(!curl) 60 if(!curl)
42 throw exception_curl(OPKELE_CP_ "failed to initialize curl"); 61 throw exception_curl(OPKELE_CP_ "failed to initialize curl");
43 string request = inm.query_string(); 62 string request = inm.query_string();
44 CURLcode r; 63 CURLcode r;
45 (r=curl.misc_sets()) 64 (r=curl.misc_sets())
46 || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str())) 65 || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str()))
47 || (r=curl.easy_setopt(CURLOPT_POST,1)) 66 || (r=curl.easy_setopt(CURLOPT_POST,1))
48 || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data())) 67 || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data()))
49 || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length())) 68 || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length()))
50 || (r=curl.set_write()); 69 || (r=curl.set_write());
51 if(r) 70 if(r)
52 throw exception_curl(OPKELE_CP_ "failed to set curly options",r); 71 throw exception_curl(OPKELE_CP_ "failed to set curly options",r);
53 if( (r=curl.easy_perform()) ) 72 if( (r=curl.easy_perform()) )
54 throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); 73 throw exception_curl(OPKELE_CP_ "failed to perform curly request",r);
55 oum.from_keyvalues(curl.response); 74 oum.from_keyvalues(curl.response);
56 } 75 }
57 76
58 77
59 assoc_t basic_RP::associate(const string& OP) { 78 assoc_t basic_RP::associate(const string& OP) {
60 util::dh_t dh = DH_new(); 79 util::dh_t dh = DH_new();
61 if(!dh) 80 if(!dh)
@@ -151,150 +170,156 @@ namespace opkele {
151 signeds.insert(f); 170 signeds.insert(f);
152 if(co==string::npos) break; 171 if(co==string::npos) break;
153 p = co+1; 172 p = co+1;
154 } 173 }
155 } 174 }
156 175
157 bool has_field(const string& n) const { 176 bool has_field(const string& n) const {
158 return signeds.find(n)!=signeds.end() && x.has_field(n); } 177 return signeds.find(n)!=signeds.end() && x.has_field(n); }
159 const string& get_field(const string& n) const { 178 const string& get_field(const string& n) const {
160 if(signeds.find(n)==signeds.end()) 179 if(signeds.find(n)==signeds.end())
161 throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed"); 180 throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed");
162 return x.get_field(n); } 181 return x.get_field(n); }
163 182
164 fields_iterator fields_begin() const { 183 fields_iterator fields_begin() const {
165 return signeds.begin(); } 184 return signeds.begin(); }
166 fields_iterator fields_end() const { 185 fields_iterator fields_end() const {
167 return signeds.end(); } 186 return signeds.end(); }
168 }; 187 };
169 188
170 static void parse_query(const string& u,string::size_type q, 189 static void parse_query(const string& u,string::size_type q,
171 map<string,string>& p) { 190 map<string,string>& p) {
172 if(q==string::npos) 191 if(q==string::npos)
173 return; 192 return;
174 assert(u[q]=='?'); 193 assert(u[q]=='?');
175 ++q; 194 ++q;
176 string::size_type l = u.size(); 195 string::size_type l = u.size();
177 while(q<l) { 196 while(q<l) {
178 string::size_type eq = u.find('=',q); 197 string::size_type eq = u.find('=',q);
179 string::size_type am = u.find('&',q); 198 string::size_type am = u.find('&',q);
180 if(am==string::npos) { 199 if(am==string::npos) {
181 if(eq==string::npos) { 200 if(eq==string::npos) {
182 p[""] = u.substr(q); 201 p[""] = u.substr(q);
183 }else{ 202 }else{
184 p[u.substr(q,eq-q)] = u.substr(eq+1); 203 p[u.substr(q,eq-q)] = u.substr(eq+1);
185 } 204 }
186 break; 205 break;
187 }else{ 206 }else{
188 if(eq==string::npos || eq>am) { 207 if(eq==string::npos || eq>am) {
189 p[""] = u.substr(q,eq-q); 208 p[""] = u.substr(q,eq-q);
190 }else{ 209 }else{
191 p[u.substr(q,eq-q)] = u.substr(eq+1,am-eq-1); 210 p[u.substr(q,eq-q)] = u.substr(eq+1,am-eq-1);
192 } 211 }
193 q = ++am; 212 q = ++am;
194 } 213 }
195 } 214 }
196 } 215 }
197 216
198 void basic_RP::id_res(const basic_openid_message& om,extension_t *ext) { 217 void basic_RP::id_res(const basic_openid_message& om,extension_t *ext) {
218 reset_vars();
199 bool o2 = om.has_field("ns") 219 bool o2 = om.has_field("ns")
200 && om.get_field("ns")==OIURI_OPENID20; 220 && om.get_field("ns")==OIURI_OPENID20;
201 if( (!o2) && om.has_field("user_setup_url")) 221 if( (!o2) && om.has_field("user_setup_url"))
202 throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided", 222 throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided",
203 om.get_field("user_setup_url")); 223 om.get_field("user_setup_url"));
204 string m = om.get_field("mode"); 224 string m = om.get_field("mode");
205 if(o2 && m=="setup_needed") 225 if(o2 && m=="setup_needed")
206 throw id_res_setup(OPKELE_CP_ "setup needed, no setup url provided"); 226 throw id_res_setup(OPKELE_CP_ "setup needed, no setup url provided");
207 if(m=="cancel") 227 if(m=="cancel")
208 throw id_res_cancel(OPKELE_CP_ "authentication cancelled"); 228 throw id_res_cancel(OPKELE_CP_ "authentication cancelled");
209 bool go_dumb=false; 229 bool go_dumb=false;
210 try { 230 try {
211 string OP = o2 231 string OP = o2
212 ?om.get_field("op_endpoint") 232 ?om.get_field("op_endpoint")
213 :get_endpoint().uri; 233 :get_endpoint().uri;
214 assoc_t assoc = retrieve_assoc( 234 assoc_t assoc = retrieve_assoc(
215 OP,om.get_field("assoc_handle")); 235 OP,om.get_field("assoc_handle"));
216 if(om.get_field("sig")!=util::base64_signature(assoc,om)) 236 if(om.get_field("sig")!=util::base64_signature(assoc,om))
217 throw id_res_mismatch(OPKELE_CP_ "signature mismatch"); 237 throw id_res_mismatch(OPKELE_CP_ "signature mismatch");
218 }catch(dumb_RP& drp) { 238 }catch(dumb_RP& drp) {
219 go_dumb=true; 239 go_dumb=true;
220 }catch(failed_lookup& e) { 240 }catch(failed_lookup& e) {
221 go_dumb=true; 241 go_dumb=true;
222 } OPKELE_RETHROW 242 } OPKELE_RETHROW
223 if(go_dumb) { 243 if(go_dumb) {
224 try { 244 try {
225 string OP = o2 245 string OP = o2
226 ?om.get_field("op_endpoint") 246 ?om.get_field("op_endpoint")
227 :get_endpoint().uri; 247 :get_endpoint().uri;
228 check_authentication(OP,om); 248 check_authentication(OP,om);
229 }catch(failed_check_authentication& fca) { 249 }catch(failed_check_authentication& fca) {
230 throw id_res_failed(OPKELE_CP_ "failed to check_authentication()"); 250 throw id_res_failed(OPKELE_CP_ "failed to check_authentication()");
231 } OPKELE_RETHROW 251 } OPKELE_RETHROW
232 } 252 }
233 signed_part_message_proxy signeds(om); 253 signed_part_message_proxy signeds(om);
234 if(o2) { 254 if(o2) {
235 check_nonce(om.get_field("op_endpoint"), 255 check_nonce(om.get_field("op_endpoint"),
236 om.get_field("response_nonce")); 256 om.get_field("response_nonce"));
237 static const char *mustsign[] = { 257 static const char *mustsign[] = {
238 "op_endpoint", "return_to", "response_nonce", "assoc_handle", 258 "op_endpoint", "return_to", "response_nonce", "assoc_handle",
239 "claimed_id", "identity" }; 259 "claimed_id", "identity" };
240 for(size_t ms=0;ms<(sizeof(mustsign)/sizeof(*mustsign));++ms) { 260 for(size_t ms=0;ms<(sizeof(mustsign)/sizeof(*mustsign));++ms) {
241 if(om.has_field(mustsign[ms]) && !signeds.has_field(mustsign[ms])) 261 if(om.has_field(mustsign[ms]) && !signeds.has_field(mustsign[ms]))
242 throw bad_input(OPKELE_CP_ string("Field '")+mustsign[ms]+"' is not signed against the specs"); 262 throw bad_input(OPKELE_CP_ string("Field '")+mustsign[ms]+"' is not signed against the specs");
243 } 263 }
244 if( ( 264 if( (
245 (om.has_field("claimed_id")?1:0) 265 (om.has_field("claimed_id")?1:0)
246 ^ 266 ^
247 (om.has_field("identity")?1:0) 267 (om.has_field("identity")?1:0)
248 )&1 ) 268 )&1 )
249 throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); 269 throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent");
250 270
251 string turl = util::rfc_3986_normalize_uri(get_this_url()); 271 string turl = util::rfc_3986_normalize_uri(get_this_url());
252 util::strip_uri_fragment_part(turl); 272 util::strip_uri_fragment_part(turl);
253 string rurl = util::rfc_3986_normalize_uri(om.get_field("return_to")); 273 string rurl = util::rfc_3986_normalize_uri(om.get_field("return_to"));
254 util::strip_uri_fragment_part(rurl); 274 util::strip_uri_fragment_part(rurl);
255 string::size_type 275 string::size_type
256 tq = turl.find('?'), rq = rurl.find('?'); 276 tq = turl.find('?'), rq = rurl.find('?');
257 if( 277 if(
258 ((tq==string::npos)?turl:turl.substr(0,tq)) 278 ((tq==string::npos)?turl:turl.substr(0,tq))
259 != 279 !=
260 ((rq==string::npos)?rurl:rurl.substr(0,rq)) 280 ((rq==string::npos)?rurl:rurl.substr(0,rq))
261 ) 281 )
262 throw id_res_bad_return_to(OPKELE_CP_ "return_to url doesn't match request url"); 282 throw id_res_bad_return_to(OPKELE_CP_ "return_to url doesn't match request url");
263 map<string,string> tp; parse_query(turl,tq,tp); 283 map<string,string> tp; parse_query(turl,tq,tp);
264 map<string,string> rp; parse_query(rurl,rq,rp); 284 map<string,string> rp; parse_query(rurl,rq,rp);
265 for(map<string,string>::const_iterator rpi=rp.begin();rpi!=rp.end();++rpi) { 285 for(map<string,string>::const_iterator rpi=rp.begin();rpi!=rp.end();++rpi) {
266 map<string,string>::const_iterator tpi = tp.find(rpi->first); 286 map<string,string>::const_iterator tpi = tp.find(rpi->first);
267 if(tpi==tp.end()) 287 if(tpi==tp.end())
268 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to is missing from the request"); 288 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to is missing from the request");
269 if(tpi->second!=rpi->second) 289 if(tpi->second!=rpi->second)
270 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to doesn't matche the request"); 290 throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to doesn't matche the request");
271 } 291 }
272 292
273 if(om.has_field("claimed_id")) { 293 if(om.has_field("claimed_id")) {
294 claimed_id = om.get_field("claimed_id");
295 identity = om.get_field("identity");
274 verify_OP( 296 verify_OP(
275 om.get_field("op_endpoint"), 297 om.get_field("op_endpoint"),
276 om.get_field("claimed_id"), 298 claimed_id, identity );
277 om.get_field("identity") );
278 } 299 }
279 300
301 }else{
302 claimed_id = get_endpoint().claimed_id;
303 /* TODO: check if this is the identity we asked for */
304 identity = om.get_field("identity");
280 } 305 }
281 if(ext) ext->rp_id_res_hook(om,signeds); 306 if(ext) ext->rp_id_res_hook(om,signeds);
282 } 307 }
283 308
284 void basic_RP::check_authentication(const string& OP, 309 void basic_RP::check_authentication(const string& OP,
285 const basic_openid_message& om){ 310 const basic_openid_message& om){
286 openid_message_t res; 311 openid_message_t res;
287 static const string checkauthmode = "check_authentication"; 312 static const string checkauthmode = "check_authentication";
288 direct_request(res,util::change_mode_message_proxy(om,checkauthmode),OP); 313 direct_request(res,util::change_mode_message_proxy(om,checkauthmode),OP);
289 if(res.has_field("is_valid")) { 314 if(res.has_field("is_valid")) {
290 if(res.get_field("is_valid")=="true") { 315 if(res.get_field("is_valid")=="true") {
291 if(res.has_field("invalidate_handle")) 316 if(res.has_field("invalidate_handle"))
292 invalidate_assoc(OP,res.get_field("invalidate_handle")); 317 invalidate_assoc(OP,res.get_field("invalidate_handle"));
293 return; 318 return;
294 } 319 }
295 } 320 }
296 throw failed_check_authentication( 321 throw failed_check_authentication(
297 OPKELE_CP_ "failed to verify response"); 322 OPKELE_CP_ "failed to verify response");
298 } 323 }
299 324
300} 325}