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