1 #include <time.h> 2 #include <cassert> 3 #include <openssl/sha.h> 4 #include <openssl/hmac.h> 5 #include <opkele/data.h> 6 #include <opkele/basic_op.h> 7 #include <opkele/exception.h> 8 #include <opkele/util.h> 9 #include <opkele/util-internal.h> 10 #include <opkele/uris.h> 11 12 namespace opkele { 13 reset_vars()14 void basic_OP::reset_vars() { 15 assoc.reset(); 16 return_to.clear(); realm.clear(); 17 claimed_id.clear(); identity.clear(); 18 invalidate_handle.clear(); 19 } 20 has_return_to() const21 bool basic_OP::has_return_to() const { 22 return !return_to.empty(); 23 } get_return_to() const24 const string& basic_OP::get_return_to() const { 25 if(return_to.empty()) 26 throw no_return_to(OPKELE_CP_ "No return_to URL provided with request"); 27 return return_to; 28 } 29 get_realm() const30 const string& basic_OP::get_realm() const { 31 assert(!realm.empty()); 32 return realm; 33 } 34 has_identity() const35 bool basic_OP::has_identity() const { 36 return !identity.empty(); 37 } get_claimed_id() const38 const string& basic_OP::get_claimed_id() const { 39 if(claimed_id.empty()) 40 throw non_identity(OPKELE_CP_ "attempting to retrieve claimed_id of non-identity related request"); 41 assert(!identity.empty()); 42 return claimed_id; 43 } get_identity() const44 const string& basic_OP::get_identity() const { 45 if(identity.empty()) 46 throw non_identity(OPKELE_CP_ "attempting to retrieve identity of non-identity related request"); 47 assert(!claimed_id.empty()); 48 return identity; 49 } 50 is_id_select() const51 bool basic_OP::is_id_select() const { 52 return identity==IDURI_SELECT20; 53 } 54 select_identity(const string & c,const string & i)55 void basic_OP::select_identity(const string& c,const string& i) { 56 claimed_id = c; identity = i; 57 } set_claimed_id(const string & c)58 void basic_OP::set_claimed_id(const string& c) { 59 claimed_id = c; 60 } 61 associate(basic_openid_message & oum,const basic_openid_message & inm)62 basic_openid_message& basic_OP::associate( 63 basic_openid_message& oum, 64 const basic_openid_message& inm) try { 65 assert(inm.get_field("mode")=="associate"); 66 util::dh_t dh; 67 util::bignum_t c_pub; 68 unsigned char key_digest[SHA256_DIGEST_LENGTH]; 69 size_t d_len = 0; 70 string sts = inm.get_field("session_type"); 71 string ats = inm.get_field("assoc_type"); 72 if(sts=="DH-SHA1" || sts=="DH-SHA256") { 73 if(!(dh = DH_new())) 74 throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); 75 c_pub = util::base64_to_bignum(inm.get_field("dh_consumer_public")); 76 BIGNUM *p; 77 BIGNUM *g; 78 try { p = util::base64_to_bignum(inm.get_field("dh_modulus")); 79 }catch(failed_lookup&) { 80 p = util::dec_to_bignum(data::_default_p); } 81 try { g = util::base64_to_bignum(inm.get_field("dh_gen")); 82 }catch(failed_lookup&) { 83 g = util::dec_to_bignum(data::_default_g); } 84 #if OPENSSL_VERSION_NUMBER >= 0x10100000L 85 DH_set0_pqg(dh, p, NULL, g); 86 #else 87 dh->p = p; 88 dh->g = g; 89 #endif 90 if(!DH_generate_key(dh)) 91 throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); 92 vector<unsigned char> ck(DH_size(dh)+1); 93 unsigned char *ckptr = &(ck.front())+1; 94 int cklen = DH_compute_key(ckptr,c_pub,dh); 95 if(cklen<0) 96 throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); 97 if(cklen && (*ckptr)&0x80) { 98 (*(--ckptr)) = 0; ++cklen; } 99 if(sts=="DH-SHA1") { 100 SHA1(ckptr,cklen,key_digest); d_len = SHA_DIGEST_LENGTH; 101 }else if(sts=="DH-SHA256") { 102 SHA256(ckptr,cklen,key_digest); d_len = SHA256_DIGEST_LENGTH; 103 }else 104 throw internal_error(OPKELE_CP_ "I thought I knew the session type"); 105 }else 106 throw unsupported(OPKELE_CP_ "Unsupported session_type"); 107 assoc_t a; 108 if(ats=="HMAC-SHA1") 109 a = alloc_assoc(ats,SHA_DIGEST_LENGTH,false); 110 else if(ats=="HMAC-SHA256") 111 a = alloc_assoc(ats,SHA256_DIGEST_LENGTH,false); 112 else 113 throw unsupported(OPKELE_CP_ "Unsupported assoc_type"); 114 oum.reset_fields(); 115 oum.set_field("ns",OIURI_OPENID20); 116 oum.set_field("assoc_type",a->assoc_type()); 117 oum.set_field("assoc_handle",a->handle()); 118 oum.set_field("expires_in",util::long_to_string(a->expires_in())); 119 secret_t secret = a->secret(); 120 if(sts=="DH-SHA1" || sts=="DH-SHA256") { 121 if(d_len != secret.size()) 122 throw bad_input(OPKELE_CP_ "Association secret and session MAC are not of the same size"); 123 oum.set_field("session_type",sts); 124 const BIGNUM *pub_key; 125 #if OPENSSL_VERSION_NUMBER >= 0x10100000L 126 DH_get0_key(dh, &pub_key, NULL); 127 #else 128 pub_key = dh->pub_key; 129 #endif 130 oum.set_field("dh_server_public",util::bignum_to_base64(pub_key)); 131 string b64; secret.enxor_to_base64(key_digest,b64); 132 oum.set_field("enc_mac_key",b64); 133 }else /* TODO: support cleartext over encrypted connection */ 134 throw unsupported(OPKELE_CP_ "Unsupported session type"); 135 return oum; 136 } catch(unsupported& u) { 137 oum.reset_fields(); 138 oum.set_field("ns",OIURI_OPENID20); 139 oum.set_field("error",u.what()); 140 oum.set_field("error_code","unsupported-type"); 141 oum.set_field("session_type","DH-SHA256"); 142 oum.set_field("assoc_type","HMAC-SHA256"); 143 return oum; 144 } 145 checkid_(const basic_openid_message & inm,extension_t * ext)146 void basic_OP::checkid_(const basic_openid_message& inm, 147 extension_t *ext) { 148 reset_vars(); 149 string modestr = inm.get_field("mode"); 150 if(modestr=="checkid_setup") 151 mode = mode_checkid_setup; 152 else if(modestr=="checkid_immediate") 153 mode = mode_checkid_immediate; 154 else 155 throw bad_input(OPKELE_CP_ "Invalid checkid_* mode"); 156 try { 157 assoc = retrieve_assoc(invalidate_handle=inm.get_field("assoc_handle")); 158 invalidate_handle.clear(); 159 }catch(failed_lookup&) { } 160 try { 161 openid2 = (inm.get_field("ns")==OIURI_OPENID20); 162 }catch(failed_lookup&) { openid2 = false; } 163 try { 164 return_to = inm.get_field("return_to"); 165 }catch(failed_lookup&) { } 166 if(openid2) { 167 try { 168 realm = inm.get_field("realm"); 169 if(realm.empty()) 170 throw failed_lookup(OPKELE_CP_ "Empty realm doesn't count"); 171 }catch(failed_lookup&) { 172 try { 173 realm = inm.get_field("trust_root"); 174 if(realm.empty()) 175 throw failed_lookup(OPKELE_CP_ "Empty trust_root doesn't count"); 176 }catch(failed_lookup&) { 177 if(return_to.empty()) 178 throw bad_input(OPKELE_CP_ 179 "Both realm and return_to are unset"); 180 realm = return_to; 181 } 182 } 183 }else{ 184 try { 185 realm = inm.get_field("trust_root"); 186 if(realm.empty()) 187 throw failed_lookup(OPKELE_CP_ "Empty trust_root doesn't count"); 188 }catch(failed_lookup&) { 189 if(return_to.empty()) 190 throw bad_input(OPKELE_CP_ 191 "Both realm and return_to are unset"); 192 realm = return_to; 193 } 194 } 195 try { 196 identity = inm.get_field("identity"); 197 try { 198 claimed_id = inm.get_field("claimed_id"); 199 }catch(failed_lookup&) { 200 if(openid2) 201 throw bad_input(OPKELE_CP_ 202 "claimed_id and identity must be either both present or both absent"); 203 claimed_id = identity; 204 } 205 }catch(failed_lookup&) { 206 if(openid2 && inm.has_field("claimed_id")) 207 throw bad_input(OPKELE_CP_ 208 "claimed_id and identity must be either both present or both absent"); 209 } 210 verify_return_to(); 211 if(ext) ext->op_checkid_hook(inm); 212 } 213 id_res(basic_openid_message & om,extension_t * ext)214 basic_openid_message& basic_OP::id_res(basic_openid_message& om, 215 extension_t *ext) { 216 assert(!return_to.empty()); 217 assert(!is_id_select()); 218 if(!assoc) { 219 assoc = alloc_assoc("HMAC-SHA256",SHA256_DIGEST_LENGTH,true); 220 } 221 time_t now = time(0); 222 struct tm gmt; gmtime_r(&now,&gmt); 223 char w3timestr[24]; 224 if(!strftime(w3timestr,sizeof(w3timestr),"%Y-%m-%dT%H:%M:%SZ",&gmt)) 225 throw failed_conversion(OPKELE_CP_ 226 "Failed to build time string for nonce" ); 227 om.set_field("ns",OIURI_OPENID20); 228 om.set_field("mode","id_res"); 229 om.set_field("op_endpoint",get_op_endpoint()); 230 string ats = "ns,mode,op_endpoint,return_to,response_nonce," 231 "assoc_handle,signed"; 232 if(!identity.empty()) { 233 om.set_field("identity",identity); 234 om.set_field("claimed_id",claimed_id); 235 ats += ",identity,claimed_id"; 236 } 237 om.set_field("return_to",return_to); 238 string nonce = w3timestr; 239 om.set_field("response_nonce",alloc_nonce(nonce)); 240 if(!invalidate_handle.empty()) { 241 om.set_field("invalidate_handle",invalidate_handle); 242 ats += ",invalidate_handle"; 243 } 244 om.set_field("assoc_handle",assoc->handle()); 245 om.add_to_signed(ats); 246 if(ext) ext->op_id_res_hook(om); 247 om.set_field("sig",util::base64_signature(assoc,om)); 248 return om; 249 } 250 cancel(basic_openid_message & om)251 basic_openid_message& basic_OP::cancel(basic_openid_message& om) { 252 assert(!return_to.empty()); 253 om.set_field("ns",OIURI_OPENID20); 254 om.set_field("mode","cancel"); 255 return om; 256 } 257 error(basic_openid_message & om,const string & err,const string & contact,const string & reference)258 basic_openid_message& basic_OP::error(basic_openid_message& om, 259 const string& err,const string& contact, 260 const string& reference ) { 261 assert(!return_to.empty()); 262 om.set_field("ns",OIURI_OPENID20); 263 om.set_field("mode","error"); 264 om.set_field("error",err); 265 if(!contact.empty()) om.set_field("contact",contact); 266 if(!reference.empty()) om.set_field("reference",reference); 267 return om; 268 } 269 setup_needed(basic_openid_message & oum,const basic_openid_message & inm)270 basic_openid_message& basic_OP::setup_needed( 271 basic_openid_message& oum,const basic_openid_message& inm) { 272 assert(mode==mode_checkid_immediate); 273 assert(!return_to.empty()); 274 if(openid2) { 275 oum.set_field("ns",OIURI_OPENID20); 276 oum.set_field("mode","setup_needed"); 277 }else{ 278 oum.set_field("mode","id_res"); 279 static const string setupmode = "checkid_setup"; 280 oum.set_field("user_setup_url", 281 util::change_mode_message_proxy(inm,setupmode) 282 .append_query(get_op_endpoint())); 283 } 284 return oum; 285 } 286 check_authentication(basic_openid_message & oum,const basic_openid_message & inm)287 basic_openid_message& basic_OP::check_authentication( 288 basic_openid_message& oum, 289 const basic_openid_message& inm) try { 290 assert(inm.get_field("mode")=="check_authentication"); 291 oum.reset_fields(); 292 oum.set_field("ns",OIURI_OPENID20); 293 bool o2; 294 try { 295 o2 = (inm.get_field("ns")==OIURI_OPENID20); 296 }catch(failed_lookup&) { o2 = false; } 297 string nonce; 298 if(o2) { 299 try { 300 if(!check_nonce(nonce = inm.get_field("response_nonce"))) 301 throw failed_check_authentication(OPKELE_CP_ "Invalid nonce"); 302 }catch(failed_lookup&) { 303 throw failed_check_authentication(OPKELE_CP_ "No nonce provided with check_authentication request"); 304 } 305 } 306 try { 307 assoc = retrieve_assoc(inm.get_field("assoc_handle")); 308 if(!assoc->stateless()) 309 throw failed_check_authentication(OPKELE_CP_ "Will not do check_authentication on a stateful handle"); 310 }catch(failed_lookup&) { 311 throw failed_check_authentication(OPKELE_CP_ "No assoc_handle or invalid assoc_handle specified with check_authentication request"); 312 } 313 static const string idresmode = "id_res"; 314 try { 315 if(util::base64_signature(assoc,util::change_mode_message_proxy(inm,idresmode))!=inm.get_field("sig")) 316 throw failed_check_authentication(OPKELE_CP_ "Signature mismatch"); 317 }catch(failed_lookup&) { 318 throw failed_check_authentication(OPKELE_CP_ "failed to calculate signature"); 319 } 320 oum.set_field("is_valid","true"); 321 try { 322 string h = inm.get_field("invalidate_handle"); 323 try { 324 assoc_t ih = retrieve_assoc(h); 325 }catch(invalid_handle& ih) { 326 oum.set_field("invalidate_handle",h); 327 }catch(failed_lookup& ih) { 328 oum.set_field("invalidate_handle",h); 329 } 330 }catch(failed_lookup&) { } 331 if(o2) { 332 assert(!nonce.empty()); 333 invalidate_nonce(nonce); 334 } 335 return oum; 336 }catch(failed_check_authentication& ) { 337 oum.set_field("is_valid","false"); 338 return oum; 339 } 340 verify_return_to()341 void basic_OP::verify_return_to() { 342 if(realm.find('#')!=string::npos) 343 throw opkele::bad_realm(OPKELE_CP_ "authentication realm contains URI fragment"); 344 if(!util::uri_matches_realm(return_to,realm)) 345 throw bad_return_to(OPKELE_CP_ "return_to URL doesn't match realm"); 346 } 347 348 } 349