summaryrefslogtreecommitdiffabout
path: root/lib
Unidiff
Diffstat (limited to 'lib') (more/less context) (show whitespace changes)
-rw-r--r--lib/.gitignore7
-rw-r--r--lib/Makefile.am24
-rw-r--r--lib/consumer.cc316
-rw-r--r--lib/data.cc11
-rw-r--r--lib/exception.cc22
-rw-r--r--lib/params.cc96
-rw-r--r--lib/secret.cc61
-rw-r--r--lib/server.cc169
-rw-r--r--lib/util.cc138
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
3Makefile.in
4libopkele.la
5.libs
6.deps
7Makefile
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 @@
1lib_LTLIBRARIES = libopkele.la
2
3INCLUDES = \
4 -I${top_srcdir}/include/ \
5 ${KONFORKA_CFLAGS} \
6 ${OPENSSL_CFLAGS} \
7 ${MIMETIC_CFLAGS} \
8 ${LIBCURL_CPPFLAGS} \
9 ${PCREPP_CFLAGS}
10LDADD = \
11 ${LIBCURL} \
12 ${PCREPP_LIBS} \
13 ${MIMETIC_LIBS} \
14 ${OPENSSL_LIBS} \
15 ${KONFORKA_LIBS}
16
17libopkele_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
25namespace 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
3namespace 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
5namespace 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
8namespace 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
7namespace 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
10namespace 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
10namespace 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}