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