author | Michael Krelin <hacker@klever.net> | 2008-01-23 21:13:27 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2008-01-23 21:13:27 (UTC) |
commit | dc5dad0709ae8dd0a4be74248a22a78e47ba90e2 (patch) (unidiff) | |
tree | eebd2a18bec33c4250036e1fd92d38cd4268777e | |
parent | 2a116bce75236e46946bb9564790c26c6a59b9a4 (diff) | |
download | libopkele-dc5dad0709ae8dd0a4be74248a22a78e47ba90e2.zip libopkele-dc5dad0709ae8dd0a4be74248a22a78e47ba90e2.tar.gz libopkele-dc5dad0709ae8dd0a4be74248a22a78e47ba90e2.tar.bz2 |
check associate reply for consistency
Reject associate replies returning secret of inconsistent with association type
length. This way severely broken OPs which return SHA1 association as SHA256
will still work in dumb mode.
Signed-off-by: Michael Krelin <hacker@klever.net>
-rw-r--r-- | lib/basic_rp.cc | 9 |
1 files changed, 6 insertions, 3 deletions
diff --git a/lib/basic_rp.cc b/lib/basic_rp.cc index 763a391..2da8416 100644 --- a/lib/basic_rp.cc +++ b/lib/basic_rp.cc | |||
@@ -1,181 +1,184 @@ | |||
1 | #include <openssl/sha.h> | 1 | #include <openssl/sha.h> |
2 | #include <openssl/hmac.h> | 2 | #include <openssl/hmac.h> |
3 | #include <opkele/basic_rp.h> | 3 | #include <opkele/basic_rp.h> |
4 | #include <opkele/exception.h> | 4 | #include <opkele/exception.h> |
5 | #include <opkele/uris.h> | 5 | #include <opkele/uris.h> |
6 | #include <opkele/data.h> | 6 | #include <opkele/data.h> |
7 | #include <opkele/util.h> | 7 | #include <opkele/util.h> |
8 | #include <opkele/curl.h> | 8 | #include <opkele/curl.h> |
9 | 9 | ||
10 | namespace opkele { | 10 | namespace opkele { |
11 | 11 | ||
12 | static void dh_get_secret( | 12 | static void dh_get_secret( |
13 | secret_t& secret, const basic_openid_message& om, | 13 | secret_t& secret, const basic_openid_message& om, |
14 | const char *exp_assoc, const char *exp_sess, | 14 | const char *exp_assoc, const char *exp_sess, |
15 | util::dh_t& dh, | 15 | util::dh_t& dh, |
16 | size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*) ) try { | 16 | size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*), |
17 | size_t exp_s_len) try { | ||
17 | if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess) | 18 | if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess) |
18 | throw bad_input(OPKELE_CP_ "Unexpected associate response"); | 19 | throw bad_input(OPKELE_CP_ "Unexpected associate response"); |
19 | util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public")); | 20 | util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public")); |
20 | vector<unsigned char> ck(DH_size(dh)+1); | 21 | vector<unsigned char> ck(DH_size(dh)+1); |
21 | unsigned char *ckptr = &(ck.front())+1; | 22 | unsigned char *ckptr = &(ck.front())+1; |
22 | int cklen = DH_compute_key(ckptr,s_pub,dh); | 23 | int cklen = DH_compute_key(ckptr,s_pub,dh); |
23 | if(cklen<0) | 24 | if(cklen<0) |
24 | throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); | 25 | throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); |
25 | if(cklen && (*ckptr)&0x80) { | 26 | if(cklen && (*ckptr)&0x80) { |
26 | (*(--ckptr))=0; ++cklen; } | 27 | (*(--ckptr))=0; ++cklen; } |
27 | unsigned char key_digest[d_len]; | 28 | unsigned char key_digest[d_len]; |
28 | secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key")); | 29 | secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key")); |
30 | if(secret.size()!=exp_s_len) | ||
31 | throw bad_input(OPKELE_CP_ "Secret length isn't consistent with association type"); | ||
29 | }catch(opkele::failed_lookup& ofl) { | 32 | }catch(opkele::failed_lookup& ofl) { |
30 | throw bad_input(OPKELE_CP_ "Incoherent response from OP"); | 33 | throw bad_input(OPKELE_CP_ "Incoherent response from OP"); |
31 | } OPKELE_RETHROW | 34 | } OPKELE_RETHROW |
32 | 35 | ||
33 | static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) { | 36 | static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) { |
34 | util::curl_pick_t curl = util::curl_pick_t::easy_init(); | 37 | util::curl_pick_t curl = util::curl_pick_t::easy_init(); |
35 | if(!curl) | 38 | if(!curl) |
36 | throw exception_curl(OPKELE_CP_ "failed to initialize curl"); | 39 | throw exception_curl(OPKELE_CP_ "failed to initialize curl"); |
37 | string request = inm.query_string(); | 40 | string request = inm.query_string(); |
38 | CURLcode r; | 41 | CURLcode r; |
39 | (r=curl.misc_sets()) | 42 | (r=curl.misc_sets()) |
40 | || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str())) | 43 | || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str())) |
41 | || (r=curl.easy_setopt(CURLOPT_POST,1)) | 44 | || (r=curl.easy_setopt(CURLOPT_POST,1)) |
42 | || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data())) | 45 | || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data())) |
43 | || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length())) | 46 | || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length())) |
44 | || (r=curl.set_write()); | 47 | || (r=curl.set_write()); |
45 | if(r) | 48 | if(r) |
46 | throw exception_curl(OPKELE_CP_ "failed to set curly options",r); | 49 | throw exception_curl(OPKELE_CP_ "failed to set curly options",r); |
47 | if( (r=curl.easy_perform()) ) | 50 | if( (r=curl.easy_perform()) ) |
48 | throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); | 51 | throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); |
49 | oum.from_keyvalues(curl.response); | 52 | oum.from_keyvalues(curl.response); |
50 | } | 53 | } |
51 | 54 | ||
52 | 55 | ||
53 | assoc_t basic_RP::associate(const string& OP) { | 56 | assoc_t basic_RP::associate(const string& OP) { |
54 | util::dh_t dh = DH_new(); | 57 | util::dh_t dh = DH_new(); |
55 | if(!dh) | 58 | if(!dh) |
56 | throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); | 59 | throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); |
57 | dh->p = util::dec_to_bignum(data::_default_p); | 60 | dh->p = util::dec_to_bignum(data::_default_p); |
58 | dh->g = util::dec_to_bignum(data::_default_g); | 61 | dh->g = util::dec_to_bignum(data::_default_g); |
59 | if(!DH_generate_key(dh)) | 62 | if(!DH_generate_key(dh)) |
60 | throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); | 63 | throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); |
61 | openid_message_t req; | 64 | openid_message_t req; |
62 | req.set_field("ns",OIURI_OPENID20); | 65 | req.set_field("ns",OIURI_OPENID20); |
63 | req.set_field("mode","associate"); | 66 | req.set_field("mode","associate"); |
64 | req.set_field("dh_modulus",util::bignum_to_base64(dh->p)); | 67 | req.set_field("dh_modulus",util::bignum_to_base64(dh->p)); |
65 | req.set_field("dh_gen",util::bignum_to_base64(dh->g)); | 68 | req.set_field("dh_gen",util::bignum_to_base64(dh->g)); |
66 | req.set_field("dh_consumer_public",util::bignum_to_base64(dh->pub_key)); | 69 | req.set_field("dh_consumer_public",util::bignum_to_base64(dh->pub_key)); |
67 | openid_message_t res; | 70 | openid_message_t res; |
68 | req.set_field("assoc_type","HMAC-SHA256"); | 71 | req.set_field("assoc_type","HMAC-SHA256"); |
69 | req.set_field("session_type","DH-SHA256"); | 72 | req.set_field("session_type","DH-SHA256"); |
70 | secret_t secret; | 73 | secret_t secret; |
71 | int expires_in; | 74 | int expires_in; |
72 | try { | 75 | try { |
73 | direct_request(res,req,OP); | 76 | direct_request(res,req,OP); |
74 | dh_get_secret( secret, res, | 77 | dh_get_secret( secret, res, |
75 | "HMAC-SHA256", "DH-SHA256", | 78 | "HMAC-SHA256", "DH-SHA256", |
76 | dh, SHA256_DIGEST_LENGTH, SHA256 ); | 79 | dh, SHA256_DIGEST_LENGTH, SHA256, SHA256_DIGEST_LENGTH ); |
77 | expires_in = util::string_to_long(res.get_field("expires_in")); | 80 | expires_in = util::string_to_long(res.get_field("expires_in")); |
78 | }catch(exception& e) { | 81 | }catch(exception& e) { |
79 | try { | 82 | try { |
80 | req.set_field("assoc_type","HMAC-SHA1"); | 83 | req.set_field("assoc_type","HMAC-SHA1"); |
81 | req.set_field("session_type","DH-SHA1"); | 84 | req.set_field("session_type","DH-SHA1"); |
82 | direct_request(res,req,OP); | 85 | direct_request(res,req,OP); |
83 | dh_get_secret( secret, res, | 86 | dh_get_secret( secret, res, |
84 | "HMAC-SHA1", "DH-SHA1", | 87 | "HMAC-SHA1", "DH-SHA1", |
85 | dh, SHA_DIGEST_LENGTH, SHA1 ); | 88 | dh, SHA_DIGEST_LENGTH, SHA1, SHA_DIGEST_LENGTH ); |
86 | expires_in = util::string_to_long(res.get_field("expires_in")); | 89 | expires_in = util::string_to_long(res.get_field("expires_in")); |
87 | }catch(bad_input& e) { | 90 | }catch(bad_input& e) { |
88 | throw dumb_RP(OPKELE_CP_ "OP failed to supply an association"); | 91 | throw dumb_RP(OPKELE_CP_ "OP failed to supply an association"); |
89 | } | 92 | } |
90 | } | 93 | } |
91 | return store_assoc( | 94 | return store_assoc( |
92 | OP, res.get_field("assoc_handle"), | 95 | OP, res.get_field("assoc_handle"), |
93 | res.get_field("assoc_type"), secret, | 96 | res.get_field("assoc_type"), secret, |
94 | expires_in ); | 97 | expires_in ); |
95 | } | 98 | } |
96 | 99 | ||
97 | basic_openid_message& basic_RP::checkid_( | 100 | basic_openid_message& basic_RP::checkid_( |
98 | basic_openid_message& rv, | 101 | basic_openid_message& rv, |
99 | mode_t mode, | 102 | mode_t mode, |
100 | const string& return_to,const string& realm, | 103 | const string& return_to,const string& realm, |
101 | extension_t *ext) { | 104 | extension_t *ext) { |
102 | rv.reset_fields(); | 105 | rv.reset_fields(); |
103 | rv.set_field("ns",OIURI_OPENID20); | 106 | rv.set_field("ns",OIURI_OPENID20); |
104 | if(mode==mode_checkid_immediate) | 107 | if(mode==mode_checkid_immediate) |
105 | rv.set_field("mode","checkid_immediate"); | 108 | rv.set_field("mode","checkid_immediate"); |
106 | else if(mode==mode_checkid_setup) | 109 | else if(mode==mode_checkid_setup) |
107 | rv.set_field("mode","checkid_setup"); | 110 | rv.set_field("mode","checkid_setup"); |
108 | else | 111 | else |
109 | throw bad_input(OPKELE_CP_ "unknown checkid_* mode"); | 112 | throw bad_input(OPKELE_CP_ "unknown checkid_* mode"); |
110 | if(realm.empty() && return_to.empty()) | 113 | if(realm.empty() && return_to.empty()) |
111 | throw bad_input(OPKELE_CP_ "At least one of realm and return_to must be non-empty"); | 114 | throw bad_input(OPKELE_CP_ "At least one of realm and return_to must be non-empty"); |
112 | if(!realm.empty()) { | 115 | if(!realm.empty()) { |
113 | rv.set_field("realm",realm); | 116 | rv.set_field("realm",realm); |
114 | rv.set_field("trust_root",realm); | 117 | rv.set_field("trust_root",realm); |
115 | } | 118 | } |
116 | if(!return_to.empty()) | 119 | if(!return_to.empty()) |
117 | rv.set_field("return_to",return_to); | 120 | rv.set_field("return_to",return_to); |
118 | const openid_endpoint_t& ep = get_endpoint(); | 121 | const openid_endpoint_t& ep = get_endpoint(); |
119 | rv.set_field("claimed_id",ep.claimed_id); | 122 | rv.set_field("claimed_id",ep.claimed_id); |
120 | rv.set_field("identity",ep.local_id); | 123 | rv.set_field("identity",ep.local_id); |
121 | try { | 124 | try { |
122 | rv.set_field("assoc_handle",find_assoc(ep.uri)->handle()); | 125 | rv.set_field("assoc_handle",find_assoc(ep.uri)->handle()); |
123 | }catch(dumb_RP& drp) { | 126 | }catch(dumb_RP& drp) { |
124 | }catch(failed_lookup& fl) { | 127 | }catch(failed_lookup& fl) { |
125 | try { | 128 | try { |
126 | rv.set_field("assoc_handle",associate(ep.uri)->handle()); | 129 | rv.set_field("assoc_handle",associate(ep.uri)->handle()); |
127 | }catch(dumb_RP& drp) { } | 130 | }catch(dumb_RP& drp) { } |
128 | } OPKELE_RETHROW | 131 | } OPKELE_RETHROW |
129 | if(ext) ext->checkid_hook(rv); | 132 | if(ext) ext->checkid_hook(rv); |
130 | return rv; | 133 | return rv; |
131 | } | 134 | } |
132 | 135 | ||
133 | class signed_part_message_proxy : public basic_openid_message { | 136 | class signed_part_message_proxy : public basic_openid_message { |
134 | public: | 137 | public: |
135 | const basic_openid_message& x; | 138 | const basic_openid_message& x; |
136 | set<string> signeds; | 139 | set<string> signeds; |
137 | 140 | ||
138 | signed_part_message_proxy(const basic_openid_message& xx) : x(xx) { | 141 | signed_part_message_proxy(const basic_openid_message& xx) : x(xx) { |
139 | const string& slist = x.get_field("signed"); | 142 | const string& slist = x.get_field("signed"); |
140 | string::size_type p = 0; | 143 | string::size_type p = 0; |
141 | while(true) { | 144 | while(true) { |
142 | string::size_type co = slist.find(',',p); | 145 | string::size_type co = slist.find(',',p); |
143 | string f = (co==string::npos) | 146 | string f = (co==string::npos) |
144 | ?slist.substr(p):slist.substr(p,co-p); | 147 | ?slist.substr(p):slist.substr(p,co-p); |
145 | signeds.insert(f); | 148 | signeds.insert(f); |
146 | if(co==string::npos) break; | 149 | if(co==string::npos) break; |
147 | p = co+1; | 150 | p = co+1; |
148 | } | 151 | } |
149 | } | 152 | } |
150 | 153 | ||
151 | bool has_field(const string& n) const { | 154 | bool has_field(const string& n) const { |
152 | return signeds.find(n)!=signeds.end() && x.has_field(n); } | 155 | return signeds.find(n)!=signeds.end() && x.has_field(n); } |
153 | const string& get_field(const string& n) const { | 156 | const string& get_field(const string& n) const { |
154 | if(signeds.find(n)==signeds.end()) | 157 | if(signeds.find(n)==signeds.end()) |
155 | throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed"); | 158 | throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed"); |
156 | return x.get_field(n); } | 159 | return x.get_field(n); } |
157 | 160 | ||
158 | fields_iterator fields_begin() const { | 161 | fields_iterator fields_begin() const { |
159 | return signeds.begin(); } | 162 | return signeds.begin(); } |
160 | fields_iterator fields_end() const { | 163 | fields_iterator fields_end() const { |
161 | return signeds.end(); } | 164 | return signeds.end(); } |
162 | }; | 165 | }; |
163 | 166 | ||
164 | static void parse_query(const string& u,string::size_type q, | 167 | static void parse_query(const string& u,string::size_type q, |
165 | map<string,string>& p) { | 168 | map<string,string>& p) { |
166 | if(q==string::npos) | 169 | if(q==string::npos) |
167 | return; | 170 | return; |
168 | assert(u[q]=='?'); | 171 | assert(u[q]=='?'); |
169 | ++q; | 172 | ++q; |
170 | string::size_type l = u.size(); | 173 | string::size_type l = u.size(); |
171 | while(q<l) { | 174 | while(q<l) { |
172 | string::size_type eq = u.find('=',q); | 175 | string::size_type eq = u.find('=',q); |
173 | string::size_type am = u.find('&',q); | 176 | string::size_type am = u.find('&',q); |
174 | if(am==string::npos) { | 177 | if(am==string::npos) { |
175 | if(eq==string::npos) { | 178 | if(eq==string::npos) { |
176 | p[""] = u.substr(q); | 179 | p[""] = u.substr(q); |
177 | }else{ | 180 | }else{ |
178 | p[u.substr(q,eq-q)] = u.substr(eq+1); | 181 | p[u.substr(q,eq-q)] = u.substr(eq+1); |
179 | } | 182 | } |
180 | break; | 183 | break; |
181 | }else{ | 184 | }else{ |