2 #include "httpauth.hpp"
14 //Can only handle 9, 32-126, 128-255.
15 std::string
quote_field(const std::string
& field
)
18 for(size_t i
= 0; i
< field
.length(); i
++) {
21 else if(field
[i
] == '\"')
30 std::string
identity(const std::string
& field
)
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
,
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.
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
]];
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
) {
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
)
123 while(get_charclass(input
, itr
) == CHRCLASS_WHITESPACE
)
125 return (itr
!= oitr
);
128 std::string
read_token(const std::string
& input
, size_t& itr
)
131 while(get_charclass(input
, itr
+ len
) == CHRCLASS_TOKEN
)
133 std::string
tmp(input
.begin() + itr
, input
.begin() + itr
+ len
);
138 std::string
read_quoted_string(const std::string
& input
, size_t& itr
, bool& error
)
140 itr
++; //Skip the initial ".
141 std::ostringstream tmp
;
143 if(is_of_class(input
, itr
, CMASK_CONTROL
| CMASK_EOS
)) {
146 } else if(is_of_class(input
, itr
, CMASK_BACKSLASH
)) {
148 if(is_of_class(input
, itr
+ 1, CMASK_CONTROL
| CMASK_EOS
)) {
152 //Skip the backslash and take next char.
156 } else if(is_of_class(input
, itr
, CMASK_DBLQUOTE
)) {
161 //Some char that is literial.
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
;
171 size_t inlen
= input
.length();
174 if(read_char(input
, itr
, ','))
177 if(read_whitespace(input
, itr
))
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.
192 } else return false; //Bad syntax.
194 //Okay, parse what follows.
196 //Now itr points to equals sign of the parameter.
197 itr
++; //Advance to start of value.
198 read_whitespace(input
, itr
); //Skip BWS.
201 if(is_of_class(input
, itr
, CMASK_TOKEN
)) {
203 pvalue
= read_token(input
, itr
);
204 } else if(is_of_class(input
, itr
, CMASK_DBLQUOTE
)) {
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
214 //Now itr points to start of parameters.
215 if(!tmp
.empty()) params
.push_back(tmp
);
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).
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.
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.
234 if(!tmp
.empty()) params
.push_back(tmp
);
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
);
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
)
255 regex_results r
= regex("[^:]+(://.*)", url
);
256 std::string url2
= r
? r
[1] : url
;
257 std::string personalization
= verb
+ " " + url2
;
261 throw std::runtime_error("Authenticator is not ready for request auth");
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
);
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()
283 uint8_t response
[32];
284 sprintf(buf
, "%u", nonce
);
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
+">");
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
)) {
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
);
319 if(pparse
.count("error")) {
320 if(pparse
["error"] == "ok")
322 else if(pparse
["error"] == "stale") {
323 //This is only an error if not reseeded this round.
326 throw std::runtime_error("Authentication is stale");
328 } else if(pparse
["error"] == "badkey") {
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");
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
) {
355 std::cerr
<< j
.first
<< "=" << j
.second
<< std::endl
;
356 std::cerr
<< "---------------------------------------" << std::endl
;