Fix some compilation errors on Mac OS X
[lsnes.git] / src / library / httpauth.cpp
blob3d5ba86d554b23c5b8c4c73263383fad51867ad6
1 #include "curve25519.hpp"
2 #include "hex.hpp"
3 #include "httpauth.hpp"
4 #include "string.hpp"
5 #include <cstdint>
6 #include <cstring>
7 #include <iostream>
8 #include <string>
9 #include <sstream>
10 #include <iomanip>
11 #include <map>
13 namespace
15 //Can only handle 9, 32-126, 128-255.
16 std::string quote_field(const std::string& field)
18 std::ostringstream x;
19 for(size_t i = 0; i < field.length(); i++) {
20 if(field[i] == '\\')
21 x << "\\\\";
22 else if(field[i] == '\"')
23 x << "\\\"";
24 else
25 x << field[i];
27 return x.str();
30 //Identity transform.
31 std::string identity(const std::string& field)
33 return field;
36 enum charclass
38 CHRCLASS_CONTROL,
39 CHRCLASS_WHITESPACE,
40 CHRCLASS_TOKEN,
41 CHRCLASS_DBLQUOTE,
42 CHRCLASS_OTHERQUOTED,
43 CHRCLASS_COMMA,
44 CHRCLASS_EQUALS,
45 CHRCLASS_BACKSLASH,
46 CHRCLASS_SLASH,
47 CHRCLASS_EOS,
50 enum charclass_mask
52 CMASK_CONTROL = 1 << CHRCLASS_CONTROL,
53 CMASK_WHITESPACE = 1 << CHRCLASS_WHITESPACE,
54 CMASK_TOKEN = 1 << CHRCLASS_TOKEN,
55 CMASK_DBLQUOTE = 1 << CHRCLASS_DBLQUOTE,
56 CMASK_OTHERQUOTED = 1 << CHRCLASS_OTHERQUOTED,
57 CMASK_COMMA = 1 << CHRCLASS_COMMA,
58 CMASK_EQUALS = 1 << CHRCLASS_EQUALS,
59 CMASK_BACKSLASH = 1 << CHRCLASS_BACKSLASH,
60 CMASK_SLASH = 1 << CHRCLASS_SLASH,
61 CMASK_EOS = 1 << CHRCLASS_EOS,
64 //Character class.
65 //0 => Controls, except HT
66 //1 => Whitespace (HT and SP).
67 //2 => Token chars (!#$%&'*+.^_`|~0-9A-Za-z-)
68 //3 => Double quote (")
69 //4 => Other quoted characters.
70 //5 => Comma
71 //6 => Equals sign.
72 //7 => Backslash
73 //8 => Slash
74 //9 => EOS.
75 uint8_t charclass[] = {
76 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
77 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, //0
78 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //1
79 1, 2, 3, 2, 2, 2, 2, 2, 4, 4, 2, 2, 5, 2, 2, 8, //2
80 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 6, 4, 4, //3
81 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, //4
82 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 7, 4, 2, 2, //5
83 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, //6
84 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 4, 2, 0, //7
85 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //8
86 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //9
87 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //A
88 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //B
89 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //C
90 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //D
91 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //E
92 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 //F
95 unsigned get_charclass(const std::string& str, size_t pos) {
96 if(pos < str.length())
97 return charclass[(uint8_t)str[pos]];
98 else
99 return CHRCLASS_EOS;
102 bool read_char(const std::string& input, size_t& itr, char ch)
104 if(input[itr] == ch) {
105 itr++;
106 return true;
108 return false;
111 bool is_of_class(const std::string& input, size_t itr, uint16_t mask)
113 return mask & (1 << get_charclass(input, itr));
116 bool read_whitespace(const std::string& input, size_t& itr)
118 size_t oitr = itr;
119 while(get_charclass(input, itr) == CHRCLASS_WHITESPACE)
120 itr++;
121 return (itr != oitr);
124 std::string read_token(const std::string& input, size_t& itr)
126 size_t len = 0;
127 while(get_charclass(input, itr + len) == CHRCLASS_TOKEN)
128 len++;
129 std::string tmp(input.begin() + itr, input.begin() + itr + len);
130 itr += len;
131 return tmp;
134 std::string read_quoted_string(const std::string& input, size_t& itr, bool& error)
136 itr++; //Skip the initial ".
137 std::ostringstream tmp;
138 while(true) {
139 if(is_of_class(input, itr, CMASK_CONTROL | CMASK_EOS)) {
140 error = true;
141 return "";
142 } else if(is_of_class(input, itr, CMASK_BACKSLASH)) {
143 //Quoted pair.
144 if(is_of_class(input, itr + 1, CMASK_CONTROL | CMASK_EOS)) {
145 error = true;
146 return "";
147 } else {
148 //Skip the backslash and take next char.
149 itr++;
150 tmp << input[itr++];
152 } else if(is_of_class(input, itr, CMASK_DBLQUOTE)) {
153 //String ends.
154 itr++;
155 return tmp.str();
156 } else {
157 //Some char that is literial.
158 tmp << input[itr++];
163 bool parse_authenticate(const std::string& input, std::list<std::map<std::string, std::string>>& params)
165 std::map<std::string, std::string> tmp;
166 size_t itr = 0;
167 size_t inlen = input.length();
168 while(itr < inlen) {
169 //Skip commas.
170 if(read_char(input, itr, ','))
171 continue;
172 //Skip whitespace.
173 if(read_whitespace(input, itr))
174 continue;
175 std::string pname = read_token(input, itr);
176 if(!pname.length()) return false; //Token is required here.
177 //Now we have two choices:
178 //1) Whitespace followed by CHRCLASS_TOKEN, CHRCLASS_SLASH, CHRCLASS_COMMA or CHRCLASS_EOS.
179 // -> This is method name.
180 //2) Possible whitespace followed by CHRCLASS_EQUALS. -> This is a parameter.
181 bool had_ws = read_whitespace(input, itr);
182 bool is_param = false;
183 if(had_ws & is_of_class(input, itr, CMASK_TOKEN | CMASK_COMMA | CMASK_SLASH | CMASK_EOS)) {
184 //This is method name.
185 } else if(is_of_class(input, itr, CMASK_EQUALS)) {
186 //This is a parameter.
187 is_param = true;
188 } else return false; //Bad syntax.
190 //Okay, parse what follows.
191 if(is_param) {
192 //Now itr points to equals sign of the parameter.
193 itr++; //Advance to start of value.
194 read_whitespace(input, itr); //Skip BWS.
195 std::string pvalue;
196 bool err = false;
197 if(is_of_class(input, itr, CMASK_TOKEN)) {
198 //Token.
199 pvalue = read_token(input, itr);
200 } else if(is_of_class(input, itr, CMASK_DBLQUOTE)) {
201 //Quoted string.
202 pvalue = read_quoted_string(input, itr, err);
203 if(err) return false; //Bad quoted string.
204 } else return false; //Bad syntax.
205 if(tmp.count(pname)) return false; //Each parameter must only occur once.
206 //We don't check for realm being quoted string (httpbis p7 says "might" on accepting
207 //token realm).
208 tmp[pname] = pvalue;
209 } else {
210 //Now itr points to start of parameters.
211 if(!tmp.empty()) params.push_back(tmp);
212 tmp.clear();
213 tmp[":method"] = pname;
214 //We have to see if this has token68 or not. It is if what follows has signature:
215 //(TOKEN|SLASH)+EQUALS*WHITESPACE*(COMMA|EOS).
216 size_t itr2 = itr;
217 while(is_of_class(input, itr2, CMASK_TOKEN | CMASK_SLASH)) itr2++;
218 if(itr == itr2) continue; //Not token68.
219 while(is_of_class(input, itr2, CMASK_EQUALS)) itr2++;
220 while(is_of_class(input, itr2, CMASK_WHITESPACE)) itr2++;
221 if(!is_of_class(input, itr2, CMASK_COMMA | CMASK_EOS)) continue; //Not token68.
222 //OK, this is token68.
223 size_t itr3 = itr;
224 while(is_of_class(input, itr3, CMASK_TOKEN | CMASK_SLASH)) itr3++;
225 tmp[":token"] = std::string(input.begin() + itr, input.begin() + itr3);
226 //Advance point of read to the COMMA/EOS.
227 itr = itr2;
230 if(!tmp.empty()) params.push_back(tmp);
231 return true;
235 dh25519_http_auth::dh25519_http_auth(const uint8_t* _privkey)
237 memcpy(privkey, _privkey, 32);
238 curve25519_clamp(privkey);
239 curve25519(pubkey, privkey, curve25519_base);
240 ready = false;
243 dh25519_http_auth::~dh25519_http_auth()
245 skein::zeroize(privkey, sizeof(privkey));
246 skein::zeroize(pubkey, sizeof(pubkey));
247 skein::zeroize(ssecret, sizeof(ssecret));
250 std::string dh25519_http_auth::format_get_session_request()
252 return "dh25519 key="+hex::b_to(pubkey,32);
255 dh25519_http_auth::request_hash dh25519_http_auth::start_request(const std::string& url, const std::string& verb)
257 unsigned _nonce;
258 regex_results r = regex("[^:]+(://.*)", url);
259 std::string url2 = r ? r[1] : url;
260 std::string personalization = verb + " " + url2;
261 char buf[32];
262 uint8_t prereq[8];
263 if(!ready)
264 throw std::runtime_error("Authenticator is not ready for request auth");
265 _nonce = nonce++;
266 sprintf(buf, "%u", _nonce);
268 skein::hash hp(skein::hash::PIPE_512, 64);
269 hp.write(ssecret, 32, skein::hash::T_KEY);
270 hp.write((const uint8_t*)personalization.c_str(), personalization.length(), skein::hash::T_PERSONALIZATION);
271 hp.write(pubkey, 32, skein::hash::T_PUBKEY);
272 hp.write((uint8_t*)buf, strlen(buf), skein::hash::T_NONCE);
273 hp.read(prereq);
275 skein::hash h(skein::hash::PIPE_512, 256);
276 h.write(ssecret, 32, skein::hash::T_KEY);
277 h.write((const uint8_t*)personalization.c_str(), personalization.length(), skein::hash::T_PERSONALIZATION);
278 h.write(pubkey, 32, skein::hash::T_PUBKEY);
279 h.write((uint8_t*)buf, strlen(buf), skein::hash::T_NONCE);
280 auto var = request_hash(id, pubkey, _nonce, h, prereq);
281 skein::zeroize(buf, sizeof(buf));
282 skein::zeroize(prereq, sizeof(prereq));
283 return var;
286 std::string dh25519_http_auth::request_hash::get_authorization()
288 char buf[32];
289 uint8_t response[32];
290 sprintf(buf, "%u", nonce);
291 h.read(response);
292 auto var = "dh25519 id="+quote_field(id)+",key="+hex::b_to(pubkey,32)+",nonce="+identity(buf)+
293 ",response="+hex::b_to(response,32)+",response2="+hex::b_to(prereq,8)+",noprotocol=1";
294 skein::zeroize(buf, sizeof(buf));
295 skein::zeroize(response, sizeof(response));
296 return var;
299 void dh25519_http_auth::parse_auth_response(const std::string& response)
301 std::list<std::map<std::string, std::string>> pparse;
302 if(!parse_authenticate(response, pparse)) {
303 throw std::runtime_error("Response parse error: <"+response+">");
305 for(auto i : pparse)
306 parse_auth_response(i);
309 void dh25519_http_auth::parse_auth_response(std::map<std::string, std::string> pparse)
311 if(pparse[":method"] != "dh25519") return;
313 //If there are id and challenge fields, use those to reseed.
314 bool stale = (pparse.count("error") && pparse["error"] == "stale");
315 bool reseeded = false;
316 if(pparse.count("id") && pparse.count("challenge") && (!ready || pparse["id"] != id || stale)) {
317 id = pparse["id"];
318 std::string challenge = pparse["challenge"];
319 if(challenge.length() != 64) goto no_reseed;
320 uint8_t _challenge[32];
321 hex::b_from(_challenge, challenge);
322 curve25519(ssecret, privkey, _challenge);
323 nonce = 0;
324 reseeded = true;
325 ready = true;
326 skein::zeroize(_challenge, sizeof(_challenge));
328 no_reseed:
329 if(pparse.count("error")) {
330 if(pparse["error"] == "ok")
331 ; //Do nothing.
332 else if(pparse["error"] == "stale") {
333 //This is only an error if not reseeded this round.
334 if(!reseeded) {
335 ready = false;
336 throw std::runtime_error("Authentication is stale");
338 } else if(pparse["error"] == "badkey") {
339 ready = false;
340 throw std::runtime_error("Client key not registed with target site");
341 } else if(pparse["error"] == "badmac")
342 throw std::runtime_error("Request failed MAC check");
343 else if(pparse["error"] == "replay")
344 throw std::runtime_error("Request was replayed");
345 else if(pparse["error"] == "syntaxerr")
346 throw std::runtime_error("Request syntax error");
347 else
348 throw std::runtime_error("Unknown error '" + pparse["error"] + "'");
352 void dh25519_http_auth::get_pubkey(uint8_t* _pubkey)
354 memcpy(_pubkey, pubkey, 32);
357 #ifdef TEST_HTTP_AUTH_PARSE
358 int main(int argc, char** argv)
360 std::list<std::map<std::string, std::string>> params;
361 bool r = parse_authenticate(argv[1], params);
362 if(!r) { std::cerr << "Parse error" << std::endl; return 1; }
363 for(auto i : params) {
364 for(auto j : i)
365 std::cerr << j.first << "=" << j.second << std::endl;
366 std::cerr << "---------------------------------------" << std::endl;
368 return 0;
370 #endif