Upload UI
[lsnes.git] / src / library / httpauth.cpp
blob088925c66e0987f0bc3ec81d35795dd976c4fafd
1 #include "httpauth.hpp"
2 #include "string.hpp"
3 #include <cstdint>
4 #include <cstring>
5 #include <iostream>
6 #include <string>
7 #include <sstream>
8 #include <iomanip>
9 #include <map>
11 namespace
13 std::string encode_hex(const uint8_t* data, size_t datasize)
15 std::ostringstream x;
16 for(size_t i = 0; i < datasize; i++)
17 x << std::hex << std::setw(2) << std::setfill('0') << (int)data[i];
18 return x.str();
21 //Can only handle 9, 32-126, 128-255.
22 std::string quote_field(const std::string& field)
24 std::ostringstream x;
25 for(size_t i = 0; i < field.length(); i++) {
26 if(field[i] == '\\')
27 x << "\\\\";
28 else if(field[i] == '\"')
29 x << "\\\"";
30 else
31 x << field[i];
33 return x.str();
36 //Identity transform.
37 std::string identity(const std::string& field)
39 return field;
42 //Character class.
43 //0 => Controls, except HT
44 //1 => Whitespace (HT and SP).
45 //2 => Token chars (!#$%&'*+.^_`|~0-9A-Za-z-)
46 //3 => Double quote (")
47 //4 => Other quoted characters.
48 //5 => Comma
49 //6 => Equals sign.
50 //7 => Backslash
51 //8 => EOS.
52 uint8_t charclass[] = {
53 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
54 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, //0
55 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //1
56 1, 2, 3, 2, 2, 2, 2, 2, 4, 4, 2, 2, 5, 2, 2, 4, //2
57 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 6, 4, 4, //3
58 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, //4
59 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 7, 4, 2, 2, //5
60 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, //6
61 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 4, 2, 0, //7
62 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //8
63 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //9
64 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //A
65 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //B
66 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //C
67 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //D
68 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, //E
69 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 //F
72 #define A_INVALID 0x0000 //Give up.
73 #define A_NOOP 0x1000 //Do nothing, not even eat the character.
74 #define A_EAT 0x2000 //Eat the character.
75 #define A_COPY_PNAME 0x3000 //Eat and copy to pname.
76 #define A_COPY_PVAL 0x4000 //Eat and copy to pvalue.
77 #define A_EMIT 0x5000 //Emit (pname,pvalue) and zeroize.
78 #define A_MASK 0xF000
79 #define A_STATE 0x0FFF
81 unsigned auth_param_parser[] = {
82 //CTRL WS TOKN DBLQ OTHQ COMM EQLS BCKS EOS
83 // 0: Skip the initial whitespace.
84 0x0000, 0x2000, 0x1001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2000,
85 // 1: Parse name.
86 0x0000, 0x1002, 0x3001, 0x0000, 0x0000, 0x0000, 0x1002, 0x0000, 0x0000,
87 // 2: Parse whitespace after name (and =)
88 0x0000, 0x2002, 0x0000, 0x0000, 0x0000, 0x0000, 0x2003, 0x0000, 0x0000,
89 // 3: Parse whitespace before value.
90 0x0000, 0x2003, 0x1004, 0x2005, 0x0000, 0x5007, 0x0000, 0x0000, 0x5000,
91 // 4: Token value.
92 0x0000, 0x5007, 0x4004, 0x0000, 0x0000, 0x5007, 0x0000, 0x0000, 0x5000,
93 // 5: Quoted-string value.
94 0x0000, 0x4005, 0x4005, 0x5009, 0x4005, 0x4005, 0x4005, 0x2006, 0x0000,
95 // 6: Quoted-string escape.
96 0x0000, 0x4005, 0x4005, 0x4005, 0x4005, 0x4005, 0x4005, 0x4005, 0x0000,
97 // 7: Whitespace after end of value.
98 0x0000, 0x2007, 0x0000, 0x0000, 0x0000, 0x2008, 0x0000, 0x0000, 0x2000,
99 // 8: Whitespace after comma.
100 0x0000, 0x2008, 0x1001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
101 // 9: Eat double quote at end of quoted string.
102 0x0000, 0x0000, 0x0000, 0x2007, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
105 unsigned get_charclass(const std::string& str, size_t pos) {
106 if(pos < str.length())
107 return charclass[(uint8_t)str[pos]];
108 else
109 return 8;
112 bool do_state_machine(const unsigned* machine, const std::string& input, size_t start,
113 std::map<std::string, std::string>& params)
115 std::string pname, pvalue;
116 unsigned state = 0;
117 while(start <= input.length()) {
118 unsigned act = machine[state * 9 + get_charclass(input, start)];
119 switch(act & A_MASK) {
120 case A_INVALID:
121 return false;
122 case A_NOOP:
123 break;
124 case A_EAT:
125 start++;
126 break;
127 case A_COPY_PNAME:
128 pname = pname + std::string(1, input[start++]);
129 break;
130 case A_COPY_PVAL:
131 pvalue = pvalue + std::string(1, input[start++]);
132 break;
133 case A_EMIT:
134 params[pname] = pvalue;
135 pname = "";
136 pvalue = "";
137 break;
139 state = act & A_STATE;
141 return true;
144 //Parse a hex char.
145 inline uint8_t hparse(char _ch)
147 uint8_t ch = _ch;
148 uint8_t itbl[] = {9,1,16,2,10,3,11,4,12,5,13,6,14,7,15,8,0};
149 return itbl[(uint8_t)(2*ch + 22*(ch>>5)) % 17];
152 //Undo hex encoding.
153 void unhex(uint8_t* buf, const std::string& str)
155 bool polarity = false;
156 size_t ptr = 0;
157 uint8_t val = 0;
158 while(ptr < str.length()) {
159 val = val * 16 + hparse(str[ptr++]);
160 if(!(polarity = !polarity)) *(buf++) = val;
165 dh25519_http_auth::dh25519_http_auth(const uint8_t* _privkey)
167 memcpy(privkey, _privkey, 32);
168 curve25519_clamp(privkey);
169 curve25519(pubkey, privkey, curve25519_base);
170 ready = false;
173 std::string dh25519_http_auth::format_get_session_request()
175 return "dh25519 key="+encode_hex(pubkey,32);
178 dh25519_http_auth::request_hash dh25519_http_auth::start_request(const std::string& url, const std::string& verb)
180 unsigned _nonce;
181 std::string personalization = verb + " " + url;
182 char buf[32];
183 uint8_t prereq[8];
184 if(!ready)
185 throw std::runtime_error("Authenticator is not ready for request auth");
186 _nonce = nonce++;
187 sprintf(buf, "%u", _nonce);
189 skein_hash hp(skein_hash::PIPE_512, 64);
190 hp.write(ssecret, 32, skein_hash::T_KEY);
191 hp.write((const uint8_t*)personalization.c_str(), personalization.length(), skein_hash::T_PERSONALIZATION);
192 hp.write(pubkey, 32, skein_hash::T_PUBKEY);
193 hp.write((uint8_t*)buf, strlen(buf), skein_hash::T_NONCE);
194 hp.read(prereq);
196 skein_hash h(skein_hash::PIPE_512, 256);
197 h.write(ssecret, 32, skein_hash::T_KEY);
198 h.write((const uint8_t*)personalization.c_str(), personalization.length(), skein_hash::T_PERSONALIZATION);
199 h.write(pubkey, 32, skein_hash::T_PUBKEY);
200 h.write((uint8_t*)buf, strlen(buf), skein_hash::T_NONCE);
201 return request_hash(id, pubkey, _nonce, h, prereq);
204 std::string dh25519_http_auth::request_hash::get_authorization()
206 char buf[32];
207 uint8_t response[32];
208 sprintf(buf, "%u", nonce);
209 h.read(response);
210 return "dh25519 id="+quote_field(id)+",key="+encode_hex(pubkey,32)+",nonce="+identity(buf)+
211 ",response="+encode_hex(response,32)+",response2="+encode_hex(prereq,8);
214 void dh25519_http_auth::parse_auth_response(const std::string& response)
216 std::map<std::string,std::string> pparse;
217 if(response.substr(0, 7) != "dh25519") return;
219 if(!do_state_machine(auth_param_parser, response, 7, pparse)) {
220 throw std::runtime_error("Response parse error: <"+response+">");
223 //If there are id and challenge fields, use those to reseed.
224 bool stale = (pparse.count("error") && pparse["error"] == "stale");
225 bool reseeded = false;
226 if(pparse.count("id") && pparse.count("challenge") && (!ready || pparse["id"] != id || stale)) {
227 id = pparse["id"];
228 std::string challenge = pparse["challenge"];
229 if(challenge.length() != 64) goto no_reseed;
230 uint8_t _challenge[32];
231 unhex(_challenge, challenge);
232 curve25519(ssecret, privkey, _challenge);
233 nonce = 0;
234 reseeded = true;
235 ready = true;
237 no_reseed:
238 if(pparse.count("error")) {
239 if(pparse["error"] == "ok")
240 ; //Do nothing.
241 else if(pparse["error"] == "stale") {
242 //This is only an error if not reseeded this round.
243 if(!reseeded) {
244 ready = false;
245 throw std::runtime_error("Authentication is stale");
247 } else if(pparse["error"] == "badkey") {
248 ready = false;
249 throw std::runtime_error("Client key not registed with target site");
250 } else if(pparse["error"] == "badmac")
251 throw std::runtime_error("Request failed MAC check");
252 else if(pparse["error"] == "replay")
253 throw std::runtime_error("Request was replayed");
254 else if(pparse["error"] == "syntaxerr")
255 throw std::runtime_error("Request syntax error");
256 else
257 throw std::runtime_error("Unknown error '" + pparse["error"] + "'");
261 void dh25519_http_auth::get_pubkey(uint8_t* _pubkey)
263 memcpy(_pubkey, pubkey, 32);