1 #include "curve25519.hpp"
3 #include "httpauth.hpp"
15 //Can only handle 9, 32-126, 128-255.
16 std::string
quote_field(const std::string
& field
)
19 for(size_t i
= 0; i
< field
.length(); i
++) {
22 else if(field
[i
] == '\"')
31 std::string
identity(const std::string
& field
)
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
,
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.
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
]];
102 bool read_char(const std::string
& input
, size_t& itr
, char ch
)
104 if(input
[itr
] == ch
) {
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
)
119 while(get_charclass(input
, itr
) == CHRCLASS_WHITESPACE
)
121 return (itr
!= oitr
);
124 std::string
read_token(const std::string
& input
, size_t& itr
)
127 while(get_charclass(input
, itr
+ len
) == CHRCLASS_TOKEN
)
129 std::string
tmp(input
.begin() + itr
, input
.begin() + itr
+ len
);
134 std::string
read_quoted_string(const std::string
& input
, size_t& itr
, bool& error
)
136 itr
++; //Skip the initial ".
137 std::ostringstream tmp
;
139 if(is_of_class(input
, itr
, CMASK_CONTROL
| CMASK_EOS
)) {
142 } else if(is_of_class(input
, itr
, CMASK_BACKSLASH
)) {
144 if(is_of_class(input
, itr
+ 1, CMASK_CONTROL
| CMASK_EOS
)) {
148 //Skip the backslash and take next char.
152 } else if(is_of_class(input
, itr
, CMASK_DBLQUOTE
)) {
157 //Some char that is literial.
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
;
167 size_t inlen
= input
.length();
170 if(read_char(input
, itr
, ','))
173 if(read_whitespace(input
, itr
))
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.
188 } else return false; //Bad syntax.
190 //Okay, parse what follows.
192 //Now itr points to equals sign of the parameter.
193 itr
++; //Advance to start of value.
194 read_whitespace(input
, itr
); //Skip BWS.
197 if(is_of_class(input
, itr
, CMASK_TOKEN
)) {
199 pvalue
= read_token(input
, itr
);
200 } else if(is_of_class(input
, itr
, CMASK_DBLQUOTE
)) {
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
210 //Now itr points to start of parameters.
211 if(!tmp
.empty()) params
.push_back(tmp
);
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).
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.
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.
230 if(!tmp
.empty()) params
.push_back(tmp
);
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
);
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
)
258 regex_results r
= regex("[^:]+(://.*)", url
);
259 std::string url2
= r
? r
[1] : url
;
260 std::string personalization
= verb
+ " " + url2
;
264 throw std::runtime_error("Authenticator is not ready for request auth");
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
);
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
));
286 std::string
dh25519_http_auth::request_hash::get_authorization()
289 uint8_t response
[32];
290 sprintf(buf
, "%u", nonce
);
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
));
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
+">");
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
)) {
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
);
326 skein::zeroize(_challenge
, sizeof(_challenge
));
329 if(pparse
.count("error")) {
330 if(pparse
["error"] == "ok")
332 else if(pparse
["error"] == "stale") {
333 //This is only an error if not reseeded this round.
336 throw std::runtime_error("Authentication is stale");
338 } else if(pparse
["error"] == "badkey") {
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");
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
) {
365 std::cerr
<< j
.first
<< "=" << j
.second
<< std::endl
;
366 std::cerr
<< "---------------------------------------" << std::endl
;