author | Michael Krelin <hacker@klever.net> | 2005-07-19 13:08:32 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2005-07-19 13:08:32 (UTC) |
commit | 4c82851dd5d5644a89d4f269079bf901f763ee33 (patch) (unidiff) | |
tree | b64c8b3c9a1be88e2a9c3f762272e0b4509ba7d9 /lib | |
parent | 907343b0c973eb295bec8795902a6d49744e9174 (diff) | |
download | libopkele-4c82851dd5d5644a89d4f269079bf901f763ee33.zip libopkele-4c82851dd5d5644a89d4f269079bf901f763ee33.tar.gz libopkele-4c82851dd5d5644a89d4f269079bf901f763ee33.tar.bz2 |
initial commit of libopkele - OpenID support library
-rw-r--r-- | lib/.gitignore | 7 | ||||
-rw-r--r-- | lib/Makefile.am | 24 | ||||
-rw-r--r-- | lib/consumer.cc | 316 | ||||
-rw-r--r-- | lib/data.cc | 11 | ||||
-rw-r--r-- | lib/exception.cc | 22 | ||||
-rw-r--r-- | lib/params.cc | 96 | ||||
-rw-r--r-- | lib/secret.cc | 61 | ||||
-rw-r--r-- | lib/server.cc | 169 | ||||
-rw-r--r-- | lib/util.cc | 138 |
9 files changed, 844 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..2325be6 --- a/dev/null +++ b/lib/.gitignore | |||
@@ -0,0 +1,7 @@ | |||
1 | *.lo | ||
2 | *.o | ||
3 | Makefile.in | ||
4 | libopkele.la | ||
5 | .libs | ||
6 | .deps | ||
7 | Makefile | ||
diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..6f3f9f3 --- a/dev/null +++ b/lib/Makefile.am | |||
@@ -0,0 +1,24 @@ | |||
1 | lib_LTLIBRARIES = libopkele.la | ||
2 | |||
3 | INCLUDES = \ | ||
4 | -I${top_srcdir}/include/ \ | ||
5 | ${KONFORKA_CFLAGS} \ | ||
6 | ${OPENSSL_CFLAGS} \ | ||
7 | ${MIMETIC_CFLAGS} \ | ||
8 | ${LIBCURL_CPPFLAGS} \ | ||
9 | ${PCREPP_CFLAGS} | ||
10 | LDADD = \ | ||
11 | ${LIBCURL} \ | ||
12 | ${PCREPP_LIBS} \ | ||
13 | ${MIMETIC_LIBS} \ | ||
14 | ${OPENSSL_LIBS} \ | ||
15 | ${KONFORKA_LIBS} | ||
16 | |||
17 | libopkele_la_SOURCES = \ | ||
18 | params.cc \ | ||
19 | util.cc \ | ||
20 | server.cc \ | ||
21 | secret.cc \ | ||
22 | data.cc \ | ||
23 | consumer.cc \ | ||
24 | exception.cc | ||
diff --git a/lib/consumer.cc b/lib/consumer.cc new file mode 100644 index 0000000..bd76b61 --- a/dev/null +++ b/lib/consumer.cc | |||
@@ -0,0 +1,316 @@ | |||
1 | #include <algorithm> | ||
2 | #include <opkele/util.h> | ||
3 | #include <opkele/exception.h> | ||
4 | #include <opkele/data.h> | ||
5 | #include <opkele/consumer.h> | ||
6 | #include <openssl/sha.h> | ||
7 | #include <openssl/hmac.h> | ||
8 | #include <mimetic/mimetic.h> | ||
9 | #include <curl/curl.h> | ||
10 | #include <pcre++.h> | ||
11 | |||
12 | #include <iostream> | ||
13 | |||
14 | /* silly mimetic */ | ||
15 | #undef PACKAGE | ||
16 | #undef PACKAGE_BUGREPORT | ||
17 | #undef PACKAGE_NAME | ||
18 | #undef PACKAGE_STRING | ||
19 | #undef PACKAGE_TARNAME | ||
20 | #undef PACKAGE_VERSION | ||
21 | #undef VERSION | ||
22 | |||
23 | #include "config.h" | ||
24 | |||
25 | namespace opkele { | ||
26 | using namespace std; | ||
27 | |||
28 | class curl_t { | ||
29 | public: | ||
30 | CURL *_c; | ||
31 | |||
32 | curl_t() : _c(0) { } | ||
33 | curl_t(CURL *c) : _c(c) { } | ||
34 | ~curl_t() throw() { if(_c) curl_easy_cleanup(_c); } | ||
35 | |||
36 | curl_t& operator=(CURL *c) { if(_c) curl_easy_cleanup(_c); _c=c; return *this; } | ||
37 | |||
38 | operator const CURL*(void) const { return _c; } | ||
39 | operator CURL*(void) { return _c; } | ||
40 | }; | ||
41 | |||
42 | static CURLcode curl_misc_sets(CURL* c) { | ||
43 | CURLcode r; | ||
44 | (r=curl_easy_setopt(c,CURLOPT_FOLLOWLOCATION,1)) | ||
45 | || (r=curl_easy_setopt(c,CURLOPT_MAXREDIRS,5)) | ||
46 | || (r=curl_easy_setopt(c,CURLOPT_DNS_CACHE_TIMEOUT,120)) | ||
47 | || (r=curl_easy_setopt(c,CURLOPT_DNS_USE_GLOBAL_CACHE,1)) | ||
48 | || (r=curl_easy_setopt(c,CURLOPT_USERAGENT,PACKAGE_NAME"/"PACKAGE_VERSION)) | ||
49 | || (r=curl_easy_setopt(c,CURLOPT_TIMEOUT,20)) | ||
50 | ; | ||
51 | return r; | ||
52 | } | ||
53 | |||
54 | static size_t _curl_tostring(void *ptr,size_t size,size_t nmemb,void *stream) { | ||
55 | string *str = (string*)stream; | ||
56 | size_t bytes = size*nmemb; | ||
57 | size_t get = min(16384-str->length(),bytes); | ||
58 | str->append((const char*)ptr,get); | ||
59 | return get; | ||
60 | } | ||
61 | |||
62 | assoc_t consumer_t::associate(const string& server) { | ||
63 | util::dh_t dh = DH_new(); | ||
64 | if(!dh) | ||
65 | throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); | ||
66 | dh->p = util::dec_to_bignum(data::_default_p); | ||
67 | dh->g = util::dec_to_bignum(data::_default_g); | ||
68 | if(!DH_generate_key(dh)) | ||
69 | throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); | ||
70 | string request = | ||
71 | "openid.mode=associate" | ||
72 | "&openid.assoc_type=HMAC-SHA1" | ||
73 | "&openid.session_type=DH-SHA1" | ||
74 | "&openid.dh_consumer_public="; | ||
75 | request += util::url_encode(util::bignum_to_base64(dh->pub_key)); | ||
76 | curl_t curl = curl_easy_init(); | ||
77 | if(!curl) | ||
78 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_init()"); | ||
79 | string response; | ||
80 | CURLcode r; | ||
81 | (r=curl_misc_sets(curl)) | ||
82 | || (r=curl_easy_setopt(curl,CURLOPT_URL,server.c_str())) | ||
83 | || (r=curl_easy_setopt(curl,CURLOPT_POST,1)) | ||
84 | || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDS,request.data())) | ||
85 | || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDSIZE,request.length())) | ||
86 | || (r=curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curl_tostring)) | ||
87 | || (r=curl_easy_setopt(curl,CURLOPT_WRITEDATA,&response)) | ||
88 | ; | ||
89 | if(r) | ||
90 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_setopt()",r); | ||
91 | if(r=curl_easy_perform(curl)) | ||
92 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_perform()",r); | ||
93 | params_t p; p.parse_keyvalues(response); | ||
94 | if(p.has_param("assoc_type") && p.get_param("assoc_type")!="HMAC-SHA1") | ||
95 | throw bad_input(OPKELE_CP_ "unsupported assoc_type"); | ||
96 | string st; | ||
97 | if(p.has_param("session_type")) st = p.get_param("session_type"); | ||
98 | if((!st.empty()) && st!="DH-SHA1") | ||
99 | throw bad_input(OPKELE_CP_ "unsupported session_type"); | ||
100 | secret_t secret; | ||
101 | if(st.empty()) { | ||
102 | secret.from_base64(p.get_param("mac_key")); | ||
103 | }else{ | ||
104 | util::bignum_t s_pub = util::base64_to_bignum(p.get_param("dh_server_public")); | ||
105 | vector<unsigned char> ck(DH_size(dh)); | ||
106 | int cklen = DH_compute_key(&(ck.front()),s_pub,dh); | ||
107 | if(cklen<0) | ||
108 | throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); | ||
109 | ck.resize(cklen); | ||
110 | // OpenID algorithm requires extra zero in case of set bit here | ||
111 | if(ck[0]&0x80) ck.insert(ck.begin(),1,0); | ||
112 | unsigned char key_sha1[SHA_DIGEST_LENGTH]; | ||
113 | SHA1(&(ck.front()),ck.size(),key_sha1); | ||
114 | secret.enxor_from_base64(key_sha1,p.get_param("enc_mac_key")); | ||
115 | } | ||
116 | int expires_in = 0; | ||
117 | if(p.has_param("expires_in")) { | ||
118 | expires_in = util::string_to_long(p.get_param("expires_in")); | ||
119 | }else if(p.has_param("issued") && p.has_param("expiry")) { | ||
120 | expires_in = util::w3c_to_time(p.get_param("expiry"))-util::w3c_to_time(p.get_param("issued")); | ||
121 | }else | ||
122 | throw bad_input(OPKELE_CP_ "no expiration information"); | ||
123 | return store_assoc(server,p.get_param("assoc_handle"),secret,expires_in); | ||
124 | } | ||
125 | |||
126 | string consumer_t::checkid_immediate(const string& identity,const string& return_to,const string& trust_root) { | ||
127 | return checkid_(mode_checkid_immediate,identity,return_to,trust_root); | ||
128 | } | ||
129 | string consumer_t::checkid_setup(const string& identity,const string& return_to,const string& trust_root) { | ||
130 | return checkid_(mode_checkid_setup,identity,return_to,trust_root); | ||
131 | } | ||
132 | string consumer_t::checkid_(mode_t mode,const string& identity,const string& return_to,const string& trust_root) { | ||
133 | params_t p; | ||
134 | if(mode==mode_checkid_immediate) | ||
135 | p["mode"]="checkid_immediate"; | ||
136 | else if(mode==mode_checkid_setup) | ||
137 | p["mode"]="checkid_setup"; | ||
138 | else | ||
139 | throw bad_input(OPKELE_CP_ "unknown checkid_* mode"); | ||
140 | string iurl = util::canonicalize_url(identity); | ||
141 | string server, delegate; | ||
142 | retrieve_links(iurl,server,delegate); | ||
143 | p["identity"] = delegate.empty()?iurl:delegate; | ||
144 | if(!trust_root.empty()) | ||
145 | p["trust_root"] = trust_root; | ||
146 | p["return_to"] = return_to; | ||
147 | try { | ||
148 | try { | ||
149 | string ah = find_assoc(server)->handle(); | ||
150 | p["assoc_handle"] = ah; | ||
151 | }catch(failed_lookup& fl) { | ||
152 | string ah = associate(server)->handle(); | ||
153 | p["assoc_handle"] = ah; | ||
154 | } | ||
155 | }catch(exception& e) { } | ||
156 | return p.append_query(server); | ||
157 | } | ||
158 | |||
159 | void consumer_t::id_res(const params_t& pin,const string& identity) { | ||
160 | if(pin.has_param("openid.user_setup_url")) | ||
161 | throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided",pin.get_param("openid.user_setup_url")); | ||
162 | string server,delegate; | ||
163 | retrieve_links(identity.empty()?pin.get_param("openid.identity"):util::canonicalize_url(identity),server,delegate); | ||
164 | try { | ||
165 | assoc_t assoc = retrieve_assoc(server,pin.get_param("openid.assoc_handle")); | ||
166 | const string& sigenc = pin.get_param("openid.sig"); | ||
167 | mimetic::Base64::Decoder b; | ||
168 | vector<unsigned char> sig; | ||
169 | mimetic::decode( | ||
170 | sigenc.begin(),sigenc.end(), b, | ||
171 | back_insert_iterator<vector<unsigned char> >(sig) ); | ||
172 | const string& slist = pin.get_param("openid.signed"); | ||
173 | string kv; | ||
174 | string::size_type p = 0; | ||
175 | while(true) { | ||
176 | string::size_type co = slist.find(',',p); | ||
177 | string f = (co==string::npos)?slist.substr(p):slist.substr(p,co-p); | ||
178 | kv += f; | ||
179 | kv += ':'; | ||
180 | f.insert(0,"openid."); | ||
181 | kv += pin.get_param(f); | ||
182 | kv += '\n'; | ||
183 | if(co==string::npos) | ||
184 | break; | ||
185 | p = co+1; | ||
186 | } | ||
187 | secret_t secret = assoc->secret(); | ||
188 | unsigned int md_len = 0; | ||
189 | unsigned char *md = HMAC( | ||
190 | EVP_sha1(), | ||
191 | &(secret.front()),secret.size(), | ||
192 | (const unsigned char *)kv.data(),kv.length(), | ||
193 | 0,&md_len); | ||
194 | if(sig.size()!=md_len || memcmp(&(sig.front()),md,md_len)) | ||
195 | throw id_res_mismatch(OPKELE_CP_ "signature mismatch"); | ||
196 | }catch(failed_lookup& e) { /* XXX: more specific? */ | ||
197 | const string& slist = pin.get_param("openid.signed"); | ||
198 | string::size_type pp = 0; | ||
199 | params_t p; | ||
200 | while(true) { | ||
201 | string::size_type co = slist.find(',',pp); | ||
202 | string f = "openid."; | ||
203 | f += (co==string::npos)?slist.substr(pp):slist.substr(pp,co-pp); | ||
204 | p[f] = pin.get_param(f); | ||
205 | if(co==string::npos) | ||
206 | break; | ||
207 | pp = co+1; | ||
208 | } | ||
209 | p["openid.assoc_handle"] = pin.get_param("openid.assoc_handle"); | ||
210 | p["openid.sig"] = pin.get_param("openid.sig"); | ||
211 | p["openid.signed"] = pin.get_param("openid.signed"); | ||
212 | try { | ||
213 | string ih = pin.get_param("openid.invalidate_handle"); | ||
214 | p["openid.invalidate_handle"] = ih; | ||
215 | }catch(failed_lookup& fl) { } | ||
216 | try { | ||
217 | check_authentication(server,p); | ||
218 | }catch(failed_check_authentication& fca) { | ||
219 | throw id_res_failed(OPKELE_CP_ "failed to check_authentication()"); | ||
220 | } | ||
221 | } | ||
222 | } | ||
223 | |||
224 | void consumer_t::check_authentication(const string& server,const params_t& p) { | ||
225 | string request = "openid.mode=check_authentication"; | ||
226 | for(params_t::const_iterator i=p.begin();i!=p.end();++i) { | ||
227 | if(i->first!="openid.mode") { | ||
228 | request += '&'; | ||
229 | request += i->first; | ||
230 | request += '='; | ||
231 | request += util::url_encode(i->second); | ||
232 | } | ||
233 | } | ||
234 | curl_t curl = curl_easy_init(); | ||
235 | if(!curl) | ||
236 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_init()"); | ||
237 | string response; | ||
238 | CURLcode r; | ||
239 | (r=curl_misc_sets(curl)) | ||
240 | || (r=curl_easy_setopt(curl,CURLOPT_URL,server.c_str())) | ||
241 | || (r=curl_easy_setopt(curl,CURLOPT_POST,1)) | ||
242 | || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDS,request.data())) | ||
243 | || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDSIZE,request.length())) | ||
244 | || (r=curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curl_tostring)) | ||
245 | || (r=curl_easy_setopt(curl,CURLOPT_WRITEDATA,&response)) | ||
246 | ; | ||
247 | if(r) | ||
248 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_setopt()",r); | ||
249 | if(r=curl_easy_perform(curl)) | ||
250 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_perform()",r); | ||
251 | params_t pp; pp.parse_keyvalues(response); | ||
252 | if(pp.has_param("invalidate_handle")) | ||
253 | invalidate_assoc(server,pp.get_param("invalidate_handle")); | ||
254 | if(pp.has_param("is_valid")) { | ||
255 | if(pp.get_param("is_valid")=="true") | ||
256 | return; | ||
257 | }else if(pp.has_param("lifetime")) { | ||
258 | if(util::string_to_long(pp.get_param("lifetime"))) | ||
259 | return; | ||
260 | } | ||
261 | throw failed_check_authentication(OPKELE_CP_ "failed to verify response"); | ||
262 | } | ||
263 | |||
264 | void consumer_t::retrieve_links(const string& url,string& server,string& delegate) { | ||
265 | server.erase(); | ||
266 | delegate.erase(); | ||
267 | curl_t curl = curl_easy_init(); | ||
268 | if(!curl) | ||
269 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_init()"); | ||
270 | string html; | ||
271 | CURLcode r; | ||
272 | (r=curl_misc_sets(curl)) | ||
273 | || (r=curl_easy_setopt(curl,CURLOPT_URL,url.c_str())) | ||
274 | || (r=curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curl_tostring)) | ||
275 | || (r=curl_easy_setopt(curl,CURLOPT_WRITEDATA,&html)) | ||
276 | ; | ||
277 | if(r) | ||
278 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_setopt()",r); | ||
279 | r = curl_easy_perform(curl); | ||
280 | if(r && r!=CURLE_WRITE_ERROR) | ||
281 | throw exception_curl(OPKELE_CP_ "failed to curl_easy_perform()",r); | ||
282 | pcrepp::Pcre bre("<body\\b",PCRE_CASELESS); | ||
283 | // strip out everything past body | ||
284 | if(bre.search(html)) | ||
285 | html.erase(bre.get_match_start()); | ||
286 | pcrepp::Pcre hdre("<head[^>]*>",PCRE_CASELESS); | ||
287 | if(!hdre.search(html)) | ||
288 | throw bad_input(OPKELE_CP_ "failed to find head"); | ||
289 | html.erase(0,hdre.get_match_end()+1); | ||
290 | pcrepp::Pcre lre("<link\\b([^>]+)>",PCRE_CASELESS), | ||
291 | rre("\\brel=['\"]([^'\"]+)['\"]",PCRE_CASELESS), | ||
292 | hre("\\bhref=['\"]([^'\"]+)['\"]",PCRE_CASELESS); | ||
293 | while(lre.search(html)) { | ||
294 | string attrs = lre[0]; | ||
295 | html.erase(0,lre.get_match_end()+1); | ||
296 | if(!(rre.search(attrs)&&hre.search(attrs))) | ||
297 | continue; | ||
298 | if(rre[0]=="openid.server") { | ||
299 | server = hre[0]; | ||
300 | if(!delegate.empty()) | ||
301 | break; | ||
302 | }else if(rre[0]=="openid.delegate") { | ||
303 | delegate = hre[0]; | ||
304 | if(!server.empty()) | ||
305 | break; | ||
306 | } | ||
307 | } | ||
308 | if(server.empty()) | ||
309 | throw failed_assertion(OPKELE_CP_ "The location has no openid.server declaration"); | ||
310 | } | ||
311 | |||
312 | assoc_t consumer_t::find_assoc(const string& server) { | ||
313 | throw failed_lookup(OPKELE_CP_ "no find_assoc() provided"); | ||
314 | } | ||
315 | |||
316 | } | ||
diff --git a/lib/data.cc b/lib/data.cc new file mode 100644 index 0000000..c040430 --- a/dev/null +++ b/lib/data.cc | |||
@@ -0,0 +1,11 @@ | |||
1 | #include <opkele/data.h> | ||
2 | |||
3 | namespace opkele { | ||
4 | |||
5 | namespace data { | ||
6 | |||
7 | const char *_default_p = "155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443"; | ||
8 | const char *_default_g = "2"; | ||
9 | |||
10 | } | ||
11 | } | ||
diff --git a/lib/exception.cc b/lib/exception.cc new file mode 100644 index 0000000..b7c1702 --- a/dev/null +++ b/lib/exception.cc | |||
@@ -0,0 +1,22 @@ | |||
1 | #include <openssl/err.h> | ||
2 | #include <curl/curl.h> | ||
3 | #include <opkele/exception.h> | ||
4 | |||
5 | namespace opkele { | ||
6 | |||
7 | exception_openssl::exception_openssl(OPKELE_E_PARS) | ||
8 | : _error(ERR_peek_last_error()), | ||
9 | _ssl_string(ERR_error_string(_error,0)), | ||
10 | exception(OPKELE_E_CONS_ w+" ["+_ssl_string+']') { | ||
11 | } | ||
12 | |||
13 | exception_curl::exception_curl(OPKELE_E_PARS) | ||
14 | : _error(CURLE_OK), | ||
15 | exception_network(OPKELE_E_CONS) { } | ||
16 | exception_curl::exception_curl(OPKELE_E_PARS,CURLcode e) | ||
17 | : _error(e), | ||
18 | _curl_string(curl_easy_strerror(e)), | ||
19 | exception_network(OPKELE_E_CONS_ w+" ["+_curl_string+']') { | ||
20 | } | ||
21 | |||
22 | } | ||
diff --git a/lib/params.cc b/lib/params.cc new file mode 100644 index 0000000..14f1a53 --- a/dev/null +++ b/lib/params.cc | |||
@@ -0,0 +1,96 @@ | |||
1 | #include <opkele/types.h> | ||
2 | #include <opkele/exception.h> | ||
3 | #include <opkele/util.h> | ||
4 | #include <openssl/sha.h> | ||
5 | #include <openssl/hmac.h> | ||
6 | #include <mimetic/mimetic.h> | ||
7 | |||
8 | namespace opkele { | ||
9 | using namespace std; | ||
10 | |||
11 | bool params_t::has_param(const string& n) const { | ||
12 | return find(n)!=end(); | ||
13 | } | ||
14 | const string& params_t::get_param(const string& n) const { | ||
15 | const_iterator i = find(n); | ||
16 | if(i==end()) | ||
17 | throw failed_lookup(OPKELE_CP_ n+": no such parameter"); | ||
18 | return i->second; | ||
19 | } | ||
20 | string& params_t::get_param(const string& n) { | ||
21 | iterator i = find(n); | ||
22 | if(i==end()) | ||
23 | throw failed_lookup(OPKELE_CP_ n+": no such parameter"); | ||
24 | return i->second; | ||
25 | } | ||
26 | |||
27 | void params_t::parse_keyvalues(const string& kv) { | ||
28 | clear(); | ||
29 | string::size_type p = 0; | ||
30 | while(true) { | ||
31 | string::size_type co = kv.find(':',p); | ||
32 | if(co==string::npos) | ||
33 | break; | ||
34 | string::size_type nl = kv.find('\n',co+1); | ||
35 | if(nl==string::npos) | ||
36 | throw bad_input(OPKELE_CP_ "malformed input"); | ||
37 | insert(value_type(kv.substr(p,co-p),kv.substr(co+1,nl-co-1))); | ||
38 | p = nl+1; | ||
39 | } | ||
40 | } | ||
41 | |||
42 | void params_t::sign(secret_t secret,string& sig,const string& slist,const char *prefix) const { | ||
43 | string kv; | ||
44 | string::size_type p = 0; | ||
45 | while(true) { | ||
46 | string::size_type co = slist.find(',',p); | ||
47 | string f = (co==string::npos)?slist.substr(p):slist.substr(p,co-p); | ||
48 | kv += f; | ||
49 | kv += ':'; | ||
50 | if(prefix) f.insert(0,prefix); | ||
51 | kv += get_param(f); | ||
52 | kv += '\n'; | ||
53 | if(co==string::npos) | ||
54 | break; | ||
55 | p = co+1; | ||
56 | } | ||
57 | unsigned int md_len = 0; | ||
58 | unsigned char *md = HMAC( | ||
59 | EVP_sha1(), | ||
60 | &(secret.front()),secret.size(), | ||
61 | (const unsigned char *)kv.data(),kv.length(), | ||
62 | 0,&md_len); | ||
63 | mimetic::Base64::Encoder b(0); | ||
64 | sig.erase(); | ||
65 | mimetic::encode( | ||
66 | md,md+md_len, b, | ||
67 | back_insert_iterator<string>(sig) ); | ||
68 | } | ||
69 | |||
70 | string params_t::append_query(const string& url,const char *prefix) const { | ||
71 | string rv = url; | ||
72 | bool p = true; | ||
73 | if(rv.find('?')==string::npos) { | ||
74 | rv += '?'; | ||
75 | p = false; | ||
76 | } | ||
77 | for(const_iterator i=begin();i!=end();++i) { | ||
78 | if(p) | ||
79 | rv += '&'; | ||
80 | else | ||
81 | p = true; | ||
82 | rv += prefix; | ||
83 | rv += i->first; | ||
84 | rv += '='; | ||
85 | rv += util::url_encode(i->second); | ||
86 | } | ||
87 | return rv; | ||
88 | } | ||
89 | |||
90 | ostream& operator << (ostream& o,const params_t& p) { | ||
91 | for(params_t::const_iterator i=p.begin();i!=p.end();++i) | ||
92 | o << i->first << ':' << i->second << '\n'; | ||
93 | return o; | ||
94 | } | ||
95 | |||
96 | } | ||
diff --git a/lib/secret.cc b/lib/secret.cc new file mode 100644 index 0000000..ae8a3c5 --- a/dev/null +++ b/lib/secret.cc | |||
@@ -0,0 +1,61 @@ | |||
1 | #include <algorithm> | ||
2 | #include <functional> | ||
3 | #include <opkele/types.h> | ||
4 | #include <opkele/exception.h> | ||
5 | #include <mimetic/mimetic.h> | ||
6 | |||
7 | namespace opkele { | ||
8 | using namespace std; | ||
9 | |||
10 | template<class __a1,class __a2,class __r> | ||
11 | struct bitwise_xor : public binary_function<__a1,__a2,__r> { | ||
12 | __r operator() (const __a1& a1,const __a2& a2) const { | ||
13 | return a1^a2; | ||
14 | } | ||
15 | }; | ||
16 | |||
17 | void secret_t::enxor_to_base64(const unsigned char *key_sha1,string& rv) const { | ||
18 | if(size()!=20) | ||
19 | throw bad_input(OPKELE_CP_ "wrong secret size"); | ||
20 | vector<unsigned char> tmp; | ||
21 | transform( | ||
22 | begin(), end(), | ||
23 | key_sha1, | ||
24 | back_insert_iterator<vector<unsigned char> >(tmp), | ||
25 | bitwise_xor<unsigned char,unsigned char,unsigned char>() ); | ||
26 | mimetic::Base64::Encoder b(0); | ||
27 | mimetic::encode( | ||
28 | tmp.begin(),tmp.end(), b, | ||
29 | back_insert_iterator<string>(rv) ); | ||
30 | } | ||
31 | |||
32 | void secret_t::enxor_from_base64(const unsigned char *key_sha1,const string& b64) { | ||
33 | mimetic::Base64::Decoder b; | ||
34 | clear(); | ||
35 | mimetic::decode( | ||
36 | b64.begin(),b64.end(), b, | ||
37 | back_insert_iterator<secret_t>(*this) ); | ||
38 | transform( | ||
39 | begin(), end(), | ||
40 | key_sha1, | ||
41 | begin(), | ||
42 | bitwise_xor<unsigned char,unsigned char,unsigned char>() ); | ||
43 | } | ||
44 | |||
45 | void secret_t::to_base64(string& rv) const { | ||
46 | if(size()!=20) | ||
47 | throw bad_input(OPKELE_CP_ "wrong secret size"); | ||
48 | mimetic::Base64::Encoder b(0); | ||
49 | mimetic::encode( | ||
50 | begin(),end(), b, | ||
51 | back_insert_iterator<string>(rv) ); | ||
52 | } | ||
53 | |||
54 | void secret_t::from_base64(const string& b64) { | ||
55 | mimetic::Base64::Decoder b; | ||
56 | mimetic::decode( | ||
57 | b64.begin(),b64.end(), b, | ||
58 | back_insert_iterator<secret_t>(*this) ); | ||
59 | } | ||
60 | |||
61 | } | ||
diff --git a/lib/server.cc b/lib/server.cc new file mode 100644 index 0000000..51d4554 --- a/dev/null +++ b/lib/server.cc | |||
@@ -0,0 +1,169 @@ | |||
1 | #include <vector> | ||
2 | #include <openssl/sha.h> | ||
3 | #include <openssl/hmac.h> | ||
4 | #include <mimetic/mimetic.h> | ||
5 | #include <opkele/util.h> | ||
6 | #include <opkele/exception.h> | ||
7 | #include <opkele/server.h> | ||
8 | #include <opkele/data.h> | ||
9 | |||
10 | namespace opkele { | ||
11 | using namespace std; | ||
12 | |||
13 | void server_t::associate(const params_t& pin,params_t& pout) { | ||
14 | util::dh_t dh; | ||
15 | util::bignum_t c_pub; | ||
16 | unsigned char key_sha1[SHA_DIGEST_LENGTH]; | ||
17 | enum { | ||
18 | sess_cleartext, | ||
19 | sess_dh_sha1 | ||
20 | } st = sess_cleartext; | ||
21 | if( | ||
22 | pin.has_param("openid.session_type") | ||
23 | && pin.get_param("openid.session_type")=="DH-SHA1" ) { | ||
24 | /* TODO: fallback to cleartext in case of exceptions here? */ | ||
25 | if(!(dh = DH_new())) | ||
26 | throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); | ||
27 | c_pub = util::base64_to_bignum(pin.get_param("openid.dh_consumer_public")); | ||
28 | if(pin.has_param("openid.dh_modulus")) | ||
29 | dh->p = util::base64_to_bignum(pin.get_param("openid.dh_modulus")); | ||
30 | else | ||
31 | dh->p = util::dec_to_bignum(data::_default_p); | ||
32 | if(pin.has_param("openid.dh_gen")) | ||
33 | dh->g = util::base64_to_bignum(pin.get_param("openid.dh_gen")); | ||
34 | else | ||
35 | dh->g = util::dec_to_bignum(data::_default_g); | ||
36 | if(!DH_generate_key(dh)) | ||
37 | throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); | ||
38 | vector<unsigned char> ck(DH_size(dh)); | ||
39 | int cklen = DH_compute_key(&(ck.front()),c_pub,dh); | ||
40 | if(cklen<0) | ||
41 | throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); | ||
42 | ck.resize(cklen); | ||
43 | // OpenID algorithm requires extra zero in case of set bit here | ||
44 | if(ck[0]&0x80) ck.insert(ck.begin(),1,0); | ||
45 | SHA1(&(ck.front()),ck.size(),key_sha1); | ||
46 | st = sess_dh_sha1; | ||
47 | } | ||
48 | assoc_t assoc = alloc_assoc(mode_associate); | ||
49 | time_t now = time(0); | ||
50 | pout.clear(); | ||
51 | pout["assoc_type"] = assoc->assoc_type(); | ||
52 | pout["assoc_handle"] = assoc->handle(); | ||
53 | /* TODO: eventually remove deprecated stuff */ | ||
54 | pout["issued"] = util::time_to_w3c(now); | ||
55 | pout["expiry"] = util::time_to_w3c(now+assoc->expires_in()); | ||
56 | pout["expires_in"] = util::long_to_string(assoc->expires_in()); | ||
57 | secret_t secret = assoc->secret(); | ||
58 | switch(st) { | ||
59 | case sess_dh_sha1: | ||
60 | pout["session_type"] = "DH-SHA1"; | ||
61 | pout["dh_server_public"] = util::bignum_to_base64(dh->pub_key); | ||
62 | secret.enxor_to_base64(key_sha1,pout["enc_mac_key"]); | ||
63 | break; | ||
64 | default: | ||
65 | secret.to_base64(pout["mac_key"]); | ||
66 | break; | ||
67 | } | ||
68 | } | ||
69 | |||
70 | void server_t::checkid_immediate(const params_t& pin,string& return_to,params_t& pout) { | ||
71 | checkid_(mode_checkid_immediate,pin,return_to,pout); | ||
72 | } | ||
73 | |||
74 | void server_t::checkid_setup(const params_t& pin,string& return_to,params_t& pout) { | ||
75 | checkid_(mode_checkid_setup,pin,return_to,pout); | ||
76 | } | ||
77 | |||
78 | void server_t::checkid_(mode_t mode,const params_t& pin,string& return_to,params_t& pout) { | ||
79 | if(mode!=mode_checkid_immediate && mode!=mode_checkid_setup) | ||
80 | throw bad_input(OPKELE_CP_ "invalid checkid_* mode"); | ||
81 | assoc_t assoc; | ||
82 | try { | ||
83 | assoc = retrieve_assoc(pin.get_param("openid.assoc_handle")); | ||
84 | }catch(failed_lookup& fl) { | ||
85 | // no handle specified or no valid handle found, going dumb | ||
86 | assoc = alloc_assoc(mode_checkid_setup); | ||
87 | } | ||
88 | string trust_root; | ||
89 | try { | ||
90 | trust_root = pin.get_param("openid.trust_root"); | ||
91 | }catch(failed_lookup& fl) { } | ||
92 | string identity = pin.get_param("openid.identity"); | ||
93 | return_to = pin.get_param("openid.return_to"); | ||
94 | validate(*assoc,pin,identity,trust_root); | ||
95 | pout.clear(); | ||
96 | pout["mode"] = "id_res"; | ||
97 | pout["assoc_handle"] = assoc->handle(); | ||
98 | if(pin.has_param("openid.assoc_handle") && assoc->stateless()) | ||
99 | pout["invalidate_handle"] = pin.get_param("openid.assoc_handle"); | ||
100 | pout["identity"] = identity; | ||
101 | pout["return_to"] = return_to; | ||
102 | /* TODO: eventually remove deprecated stuff */ | ||
103 | time_t now = time(0); | ||
104 | pout["issued"] = util::time_to_w3c(now); | ||
105 | pout["valid_to"] = util::time_to_w3c(now+120); | ||
106 | pout["exipres_in"] = "120"; | ||
107 | pout.sign(assoc->secret(),pout["sig"],pout["signed"]="mode,identity,return_to"); | ||
108 | } | ||
109 | |||
110 | void server_t::check_authentication(const params_t& pin,params_t& pout) { | ||
111 | vector<unsigned char> sig; | ||
112 | mimetic::Base64::Decoder b; | ||
113 | const string& sigenc = pin.get_param("openid.sig"); | ||
114 | mimetic::decode( | ||
115 | sigenc.begin(),sigenc.end(), b, | ||
116 | back_insert_iterator<vector<unsigned char> >(sig)); | ||
117 | assoc_t assoc; | ||
118 | try { | ||
119 | assoc = retrieve_assoc(pin.get_param("openid.assoc_handle")); | ||
120 | }catch(failed_lookup& fl) { | ||
121 | throw failed_assertion(OPKELE_CP_ "invalid handle or handle not specified"); | ||
122 | } | ||
123 | if(!assoc->stateless()) | ||
124 | throw stateful_handle(OPKELE_CP_ "will not do check_authentication on a stateful handle"); | ||
125 | const string& slist = pin.get_param("openid.signed"); | ||
126 | string kv; | ||
127 | string::size_type p =0; | ||
128 | while(true) { | ||
129 | string::size_type co = slist.find(',',p); | ||
130 | string f = (co==string::npos)?slist.substr(p):slist.substr(p,co-p); | ||
131 | kv += f; | ||
132 | kv += ':'; | ||
133 | if(f=="mode") | ||
134 | kv += "id_res"; | ||
135 | else { | ||
136 | f.insert(0,"openid."); | ||
137 | kv += pin.get_param(f); | ||
138 | } | ||
139 | kv += '\n'; | ||
140 | if(co==string::npos) | ||
141 | break; | ||
142 | p = co+1; | ||
143 | } | ||
144 | secret_t secret = assoc->secret(); | ||
145 | unsigned int md_len = 0; | ||
146 | unsigned char *md = HMAC( | ||
147 | EVP_sha1(), | ||
148 | &(secret.front()),secret.size(), | ||
149 | (const unsigned char *)kv.data(),kv.length(), | ||
150 | 0,&md_len); | ||
151 | pout.clear(); | ||
152 | if(sig.size()==md_len && !memcmp(&(sig.front()),md,md_len)) { | ||
153 | pout["is_valid"]="true"; | ||
154 | pout["lifetime"]="60"; /* TODO: eventually remove deprecated stuff */ | ||
155 | }else{ | ||
156 | pout["is_valid"]="false"; | ||
157 | pout["lifetime"]="0"; /* TODO: eventually remove deprecated stuff */ | ||
158 | } | ||
159 | if(pin.has_param("openid.invalidate_handle")) { | ||
160 | string h = pin.get_param("openid.invalidate_handle"); | ||
161 | try { | ||
162 | assoc_t assoc = retrieve_assoc(h); | ||
163 | }catch(invalid_handle& ih) { | ||
164 | pout["invalidate_handle"] = h; | ||
165 | }catch(failed_lookup& fl) { } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | } | ||
diff --git a/lib/util.cc b/lib/util.cc new file mode 100644 index 0000000..1e7335c --- a/dev/null +++ b/lib/util.cc | |||
@@ -0,0 +1,138 @@ | |||
1 | #include <errno.h> | ||
2 | #include <cassert> | ||
3 | #include <vector> | ||
4 | #include <string> | ||
5 | #include <mimetic/mimetic.h> | ||
6 | #include <curl/curl.h> | ||
7 | #include "opkele/util.h" | ||
8 | #include "opkele/exception.h" | ||
9 | |||
10 | namespace opkele { | ||
11 | using namespace std; | ||
12 | |||
13 | namespace util { | ||
14 | |||
15 | /* | ||
16 | * big numerics | ||
17 | */ | ||
18 | |||
19 | BIGNUM *base64_to_bignum(const string& b64) { | ||
20 | vector<unsigned char> bin; | ||
21 | mimetic::Base64::Decoder b; | ||
22 | mimetic::decode( | ||
23 | b64.begin(),b64.end(), b, | ||
24 | back_insert_iterator<vector<unsigned char> >(bin) ); | ||
25 | BIGNUM *rv = BN_bin2bn(&(bin.front()),bin.size(),0); | ||
26 | if(!rv) | ||
27 | throw failed_conversion(OPKELE_CP_ "failed to BN_bin2bn()"); | ||
28 | return rv; | ||
29 | } | ||
30 | |||
31 | BIGNUM *dec_to_bignum(const string& dec) { | ||
32 | BIGNUM *rv = 0; | ||
33 | if(!BN_dec2bn(&rv,dec.c_str())) | ||
34 | throw failed_conversion(OPKELE_CP_ "failed to BN_dec2bn()"); | ||
35 | return rv; | ||
36 | } | ||
37 | |||
38 | string bignum_to_base64(const BIGNUM *bn) { | ||
39 | vector<unsigned char> bin(BN_num_bytes(bn)); | ||
40 | int l = BN_bn2bin(bn,&(bin.front())); | ||
41 | string rv; | ||
42 | mimetic::Base64::Encoder b(0); | ||
43 | mimetic::encode( | ||
44 | bin.begin(),bin.begin()+l, b, | ||
45 | back_insert_iterator<string>(rv) ); | ||
46 | return rv; | ||
47 | } | ||
48 | |||
49 | /* | ||
50 | * w3c times | ||
51 | */ | ||
52 | |||
53 | string time_to_w3c(time_t t) { | ||
54 | struct tm tm_t; | ||
55 | if(!gmtime_r(&t,&tm_t)) | ||
56 | throw failed_conversion(OPKELE_CP_ "failed to BN_dec2bn()"); | ||
57 | char rv[25]; | ||
58 | if(!strftime(rv,sizeof(rv)-1,"%Y-%m-%dT%H:%M:%SZ",&tm_t)) | ||
59 | throw failed_conversion(OPKELE_CP_ "failed to strftime()"); | ||
60 | return rv; | ||
61 | } | ||
62 | |||
63 | time_t w3c_to_time(const string& w) { | ||
64 | struct tm tm_t; | ||
65 | memset(&tm_t,0,sizeof(tm_t)); | ||
66 | if( | ||
67 | sscanf( | ||
68 | w.c_str(), | ||
69 | "%04d-%02d-%02dT%02d:%02d:%02dZ", | ||
70 | &tm_t.tm_year,&tm_t.tm_mon,&tm_t.tm_mday, | ||
71 | &tm_t.tm_hour,&tm_t.tm_min,&tm_t.tm_sec | ||
72 | ) != 6 ) | ||
73 | throw failed_conversion(OPKELE_CP_ "failed to sscanf()"); | ||
74 | tm_t.tm_mon--; | ||
75 | tm_t.tm_year-=1900; | ||
76 | time_t rv = mktime(&tm_t); | ||
77 | if(rv==(time_t)-1) | ||
78 | throw failed_conversion(OPKELE_CP_ "failed to mktime()"); | ||
79 | return rv; | ||
80 | } | ||
81 | |||
82 | /* | ||
83 | * | ||
84 | */ | ||
85 | |||
86 | string canonicalize_url(const string& url) { | ||
87 | string rv = url; | ||
88 | // strip leading and trailing spaces | ||
89 | string::size_type i = rv.find_first_not_of(" \t\r\n"); | ||
90 | if(i==string::npos) | ||
91 | throw bad_input(OPKELE_CP_ "empty URL"); | ||
92 | if(i) | ||
93 | rv.erase(0,i); | ||
94 | i = rv.find_last_not_of(" \t\r\n"); | ||
95 | assert(i!=string::npos); | ||
96 | if(i<(rv.length()-1)) | ||
97 | rv.erase(i+1); | ||
98 | // add missing http:// | ||
99 | i = rv.find("://"); | ||
100 | if(i==string::npos) { // primitive. but do we need more? | ||
101 | rv.insert(0,"http://"); | ||
102 | i = sizeof("http://")-1; | ||
103 | }else{ | ||
104 | i += sizeof("://")-1; | ||
105 | } | ||
106 | if(rv.find('/',i)==string::npos) | ||
107 | rv += '/'; | ||
108 | return rv; | ||
109 | } | ||
110 | |||
111 | string url_encode(const string& str) { | ||
112 | char * t = curl_escape(str.c_str(),str.length()); | ||
113 | if(!t) | ||
114 | throw failed_conversion(OPKELE_CP_ "failed to curl_escape()"); | ||
115 | string rv(t); | ||
116 | curl_free(t); | ||
117 | return rv; | ||
118 | } | ||
119 | |||
120 | string long_to_string(long l) { | ||
121 | char rv[32]; | ||
122 | int r=snprintf(rv,sizeof(rv),"%ld",l); | ||
123 | if(r<0 || r>=sizeof(rv)) | ||
124 | throw failed_conversion(OPKELE_CP_ "failed to snprintf()"); | ||
125 | return rv; | ||
126 | } | ||
127 | |||
128 | long string_to_long(const string& s) { | ||
129 | char *endptr = 0; | ||
130 | long rv = strtol(s.c_str(),&endptr,10); | ||
131 | if((!endptr) || endptr==s.c_str()) | ||
132 | throw failed_conversion(OPKELE_CP_ "failed to strtol()"); | ||
133 | return rv; | ||
134 | } | ||
135 | |||
136 | } | ||
137 | |||
138 | } | ||