Electroneum
Loading...
Searching...
No Matches
http_auth.cpp
Go to the documentation of this file.
1// Copyrights(c) 2017-2021, The Electroneum Project
2// Copyrights(c) 2014-2019, The Monero Project
3//
4// All rights reserved.
5//
6// Redistribution and use in source and binary forms, with or without modification, are
7// permitted provided that the following conditions are met:
8//
9// 1. Redistributions of source code must retain the above copyright notice, this list of
10// conditions and the following disclaimer.
11//
12// 2. Redistributions in binary form must reproduce the above copyright notice, this list
13// of conditions and the following disclaimer in the documentation and/or other
14// materials provided with the distribution.
15//
16// 3. Neither the name of the copyright holder nor the names of its contributors may be
17// used to endorse or promote products derived from this software without specific
18// prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
21// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
23// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
28// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29#include "net/http_auth.h"
30
31#include <array>
32#include <boost/algorithm/string/find_iterator.hpp>
33#include <boost/algorithm/string/predicate.hpp>
34#include <boost/fusion/adapted/std_tuple.hpp>
35#include <boost/fusion/algorithm/iteration/for_each.hpp>
36#include <boost/fusion/algorithm/iteration/iter_fold.hpp>
37#include <boost/fusion/algorithm/query/any.hpp>
38#include <boost/fusion/iterator/distance.hpp>
39#include <boost/fusion/iterator/value_of.hpp>
40#include <boost/fusion/sequence/intrinsic/begin.hpp>
41#include <boost/fusion/sequence/intrinsic/size.hpp>
42#include <boost/range/algorithm/copy.hpp>
43#include <boost/range/algorithm/find_if.hpp>
44#include <boost/range/iterator_range_core.hpp>
45#include <boost/range/join.hpp>
46#include <boost/spirit/include/karma_generate.hpp>
47#include <boost/spirit/include/karma_uint.hpp>
48#include <boost/spirit/include/qi_alternative.hpp>
49#include <boost/spirit/include/qi_and_predicate.hpp>
50#include <boost/spirit/include/qi_char.hpp>
51#include <boost/spirit/include/qi_char_class.hpp>
52#include <boost/spirit/include/qi_difference.hpp>
53#include <boost/spirit/include/qi_kleene.hpp>
54#include <boost/spirit/include/qi_parse.hpp>
55#include <boost/spirit/include/qi_plus.hpp>
56#include <boost/spirit/include/qi_no_case.hpp>
57#include <boost/spirit/include/qi_not_predicate.hpp>
58#include <boost/spirit/include/qi_raw.hpp>
59#include <boost/spirit/include/qi_rule.hpp>
60#include <boost/spirit/include/qi_sequence.hpp>
61#include <boost/spirit/include/qi_string.hpp>
62#include <boost/spirit/include/qi_symbols.hpp>
63#include <boost/spirit/include/qi_uint.hpp>
64#include <cassert>
65#include <iterator>
66#include <limits>
67#include <tuple>
68#include <type_traits>
69
70#include "hex.h"
71#include "md5_l.h"
72#include "string_coding.h"
73
74/* This file uses the `u8` prefix and specifies all chars by ASCII numeric
75value. This is for maximum portability - C++ does not actually specify ASCII
76as the encoding type for unprefixed string literals, etc. Although rare, the
77effort required to support rare compiler encoding types is low.
78
79Also be careful of qi::ascii character classes (`qi::asci::alpha`, etc.) -
80non-ASCII characters will cause undefined behavior in the table lookup until
81boost 1.60. The expression `&qi::ascii::char_` will fail on non-ASCII
82characters without "consuming" the input character. */
83
84namespace
85{
86 namespace http = epee::net_utils::http;
87
88 // string_ref is only constexpr if length is given
89 template<std::size_t N>
90 constexpr boost::string_ref ceref(const char (&arg)[N])
91 {
92 return boost::string_ref(arg, N - 1);
93 }
94
95 constexpr const auto client_auth_field = ceref(u8"Authorization");
96 constexpr const auto server_auth_field = ceref(u8"WWW-authenticate");
97 constexpr const auto auth_realm = ceref(u8"electroneum-rpc");
98 constexpr const char comma = 44;
99 constexpr const char equal_sign = 61;
100 constexpr const char quote = 34;
101 constexpr const char zero = 48;
102 constexpr const auto sess_algo = ceref(u8"-sess");
103
104 constexpr const unsigned client_reserve_size = 512;
105
107
108 struct md5_
109 {
110 static constexpr const boost::string_ref name = ceref(u8"MD5");
111
112 struct update
113 {
114 template<typename T>
115 void operator()(const T& arg) const
116 {
117 const boost::iterator_range<const char*> data(boost::as_literal(arg));
118 md5::MD5Update(
119 std::addressof(ctx),
120 reinterpret_cast<const std::uint8_t*>(data.begin()),
121 data.size()
122 );
123 }
124 void operator()(const std::string& arg) const
125 {
126 (*this)(boost::string_ref(arg));
127 }
128 void operator()(const epee::wipeable_string& arg) const
129 {
130 md5::MD5Update(
131 std::addressof(ctx),
132 reinterpret_cast<const std::uint8_t*>(arg.data()),
133 arg.size()
134 );
135 }
136
137 md5::MD5_CTX& ctx;
138 };
139
140 template<typename... T>
141 std::array<char, 32> operator()(const T&... args) const
142 {
143 md5::MD5_CTX ctx{};
144 md5::MD5Init(std::addressof(ctx));
145 boost::fusion::for_each(std::tie(args...), update{ctx});
146
147 std::array<std::uint8_t, 16> digest{{}};
148 md5::MD5Final(digest.data(), std::addressof(ctx));
149 return epee::to_hex::array(digest);
150 }
151 };
152 constexpr const boost::string_ref md5_::name;
153
155 constexpr const std::tuple<md5_> digest_algorithms{};
156
158
159 struct ascii_tolower_
160 {
161 template<typename Char>
162 constexpr Char operator()(Char value) const noexcept
163 {
164 static_assert(std::is_integral<Char>::value, "only integral types supported");
165 return (65 <= value && value <= 90) ? (value + 32) : value;
166 }
167 };
168 constexpr const ascii_tolower_ ascii_tolower{};
169
170 struct ascii_iequal_
171 {
172 template<typename Char>
173 constexpr bool operator()(Char left, Char right) const noexcept
174 {
175 return ascii_tolower(left) == ascii_tolower(right);
176 }
177 };
178 constexpr const ascii_iequal_ ascii_iequal{};
179
180 struct http_list_separator_
181 {
182 template<typename Char>
183 bool operator()(Char value) const noexcept
184 {
185 static_assert(std::is_integral<Char>::value, "only integral types supported");
186 return boost::spirit::char_encoding::ascii::isascii_(value) &&
187 (value == comma || boost::spirit::char_encoding::ascii::isspace(value));
188 }
189 };
190 constexpr const http_list_separator_ http_list_separator{};
191
192 std::string to_string(boost::iterator_range<const char*> source)
193 {
194 return {source.begin(), source.size()};
195 }
196
197 template<typename T>
198 void add_first_field(std::string& str, const char* const name, const T& value)
199 {
200 str.append(name);
201 str.push_back(equal_sign);
202 boost::copy(value, std::back_inserter(str));
203 }
204
205 template<typename T>
206 void add_field(std::string& str, const char* const name, const T& value)
207 {
208 str.push_back(comma);
209 add_first_field(str, name, value);
210 }
211
212 template<typename T>
213 using quoted_result = boost::joined_range<
214 const boost::joined_range<const boost::string_ref, const T>, const boost::string_ref
215 >;
216
217 template<typename T>
218 quoted_result<T> quoted(const T& arg)
219 {
220 return boost::range::join(boost::range::join(ceref(u8"\""), arg), ceref(u8"\""));
221 }
222
224
225 template<typename Digest>
226 typename std::result_of<Digest()>::type generate_a1(
227 Digest digest, const http::login& creds, const boost::string_ref realm)
228 {
229 return digest(creds.username, u8":", realm, u8":", creds.password);
230 }
231
232 template<typename Digest>
233 typename std::result_of<Digest()>::type generate_a1(
234 Digest digest, const http::http_client_auth::session& user)
235 {
236 return generate_a1(std::move(digest), user.credentials, user.server.realm);
237 }
238
239 template<typename T>
240 void init_client_value(std::string& str,
241 const boost::string_ref algorithm, const http::http_client_auth::session& user,
242 const boost::string_ref uri, const T& response)
243 {
244 str.append(u8"Digest ");
245 add_first_field(str, u8"algorithm", algorithm);
246 add_field(str, u8"nonce", quoted(user.server.nonce));
247 add_field(str, u8"realm", quoted(user.server.realm));
248 add_field(str, u8"response", quoted(response));
249 add_field(str, u8"uri", quoted(uri));
250 add_field(str, u8"username", quoted(user.credentials.username));
251 if (!user.server.opaque.empty())
252 add_field(str, u8"opaque", quoted(user.server.opaque));
253 }
254
256 template<typename Digest>
257 struct old_algorithm
258 {
259 explicit old_algorithm(Digest digest_) : digest(std::move(digest_)) {}
260
261 std::string operator()(const http::http_client_auth::session& user,
262 const boost::string_ref method, const boost::string_ref uri) const
263 {
264 const auto response = digest(
265 generate_a1(digest, user), u8":", user.server.nonce, u8":", digest(method, u8":", uri)
266 );
267 std::string out{};
268 out.reserve(client_reserve_size);
269 init_client_value(out, Digest::name, user, uri, response);
270 return out;
271 }
272 private:
273 Digest digest;
274 };
275
277 template<typename Digest>
278 struct auth_algorithm
279 {
280 explicit auth_algorithm(Digest digest_) : digest(std::move(digest_)) {}
281
282 std::string operator()(const http::http_client_auth::session& user,
283 const boost::string_ref method, const boost::string_ref uri) const
284 {
285 namespace karma = boost::spirit::karma;
286 using counter_type = decltype(user.counter);
287 static_assert(
288 std::numeric_limits<counter_type>::radix == 2, "unexpected radix for counter"
289 );
290 static_assert(
291 std::numeric_limits<counter_type>::digits <= 32,
292 "number of digits will cause underflow on padding below"
293 );
294
295 std::string out{};
296 out.reserve(client_reserve_size);
297
298 karma::generate(std::back_inserter(out), karma::hex(user.counter));
299 out.insert(out.begin(), 8 - out.size(), zero); // zero left pad
300 if (out.size() != 8)
301 return {};
302
303 std::array<char, 8> nc{{}};
304 boost::copy(out, nc.data());
305 const auto response = digest(
306 generate_a1(digest, user), u8":", user.server.nonce, u8":", nc, u8"::auth:", digest(method, u8":", uri)
307 );
308 out.clear();
309 init_client_value(out, Digest::name, user, uri, response);
310 add_field(out, u8"qop", ceref(u8"auth"));
311 add_field(out, u8"nc", nc);
312 return out;
313 }
314
315 private:
316 Digest digest;
317 };
318
320 struct auth_message
321 {
322 using iterator = const char*;
323 enum status{ kFail = 0, kStale, kPass };
324
326 static status verify(const boost::string_ref method, const boost::string_ref request,
327 const http::http_server_auth::session& user)
328 {
329 const auto parsed = parse(request);
330 if (parsed &&
331 boost::equals(parsed->username, user.credentials.username) &&
332 boost::fusion::any(digest_algorithms, has_valid_response{*parsed, user, method}))
333 {
334 if (boost::equals(parsed->nonce, user.nonce))
335 {
336 // RFC 2069 format does not verify nc value - allow just once
337 if (user.counter == 1 || (!parsed->qop.empty() && parsed->counter() == user.counter))
338 {
339 return kPass;
340 }
341 }
342 return kStale;
343 }
344 return kFail;
345 }
346
348 static http::http_client_auth::session::keys extract(
349 const http::http_response_info& response, const bool is_first)
350 {
351 using field = std::pair<std::string, std::string>;
352
353 server_parameters best{};
354
355 const std::list<field>& fields = response.m_header_info.m_etc_fields;
356 auto current = fields.begin();
357 const auto end = fields.end();
358 while (true)
359 {
360 current = std::find_if(current, end, [] (const field& value) {
361 return boost::equals(server_auth_field, value.first, ascii_iequal);
362 });
363 if (current == end)
364 break;
365
366 const auto parsed = parse(current->second);
367 if (parsed)
368 {
369 server_parameters local_best = parsed->algorithm.empty() ?
370 server_parameters{*parsed, boost::fusion::find<md5_>(digest_algorithms)} :
371 boost::fusion::iter_fold(digest_algorithms, server_parameters{}, matches_algorithm{*parsed});
372
373 if (local_best.index < best.index)
374 best = std::move(local_best);
375 }
376 ++current;
377 }
378 if (is_first || boost::equals(best.stale, ceref(u8"true"), ascii_iequal))
379 return best.take();
380 return {}; // authentication failed with bad user/pass
381 }
382
383 private:
384 explicit auth_message()
385 : algorithm()
386 , cnonce()
387 , nc()
388 , nonce()
389 , qop()
390 , realm()
391 , response()
392 , stale()
393 , uri()
394 , username() {
395 }
396
397 static boost::optional<auth_message> parse(const boost::string_ref request)
398 {
399 struct parser
400 {
401 using field_parser = std::function<bool(const parser&, iterator&, iterator, auth_message&)>;
402
403 explicit parser() : field_table(), skip_whitespace(), header(), quoted_string(), token(), fields() {
404 using namespace std::placeholders;
405 namespace qi = boost::spirit::qi;
406
407 struct parse_nc
408 {
409 bool operator()(const parser&, iterator& current, const iterator end, auth_message& result) const
410 {
411 return qi::parse(
412 current, end,
413 (qi::raw[qi::uint_parser<std::uint32_t, 16, 8, 8>{}]),
414 result.nc
415 );
416 }
417 };
418 struct parse_token
419 {
420 bool operator()(const parser& parse, iterator& current, const iterator end,
421 boost::iterator_range<iterator>& result) const
422 {
423 return qi::parse(current, end, parse.token, result);
424 }
425 };
426 struct parse_string
427 {
428 bool operator()(const parser& parse, iterator& current, const iterator end,
429 boost::iterator_range<iterator>& result) const
430 {
431 return qi::parse(current, end, parse.quoted_string, result);
432 }
433 bool operator()(const parser& parse, iterator& current, const iterator end) const
434 {
435 return qi::parse(current, end, parse.quoted_string);
436 }
437 };
438 struct parse_response
439 {
440 bool operator()(const parser&, iterator& current, const iterator end, auth_message& result) const
441 {
442 using byte = qi::uint_parser<std::uint8_t, 16, 2, 2>;
443 return qi::parse(
444 current, end,
445 (qi::lit(quote) >> qi::raw[+(byte{})] >> qi::lit(quote)),
446 result.response
447 );
448 }
449 };
450
451 field_table.add
452 (u8"algorithm", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::algorithm, _4)))
453 (u8"cnonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::cnonce, _4)))
454 (u8"domain", std::bind(parse_string{}, _1, _2, _3)) // ignore field
455 (u8"nc", parse_nc{})
456 (u8"nonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::nonce, _4)))
457 (u8"opaque", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::opaque, _4)))
458 (u8"qop", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::qop, _4)))
459 (u8"realm", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::realm, _4)))
460 (u8"response", parse_response{})
461 (u8"stale", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::stale, _4)))
462 (u8"uri", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::uri, _4)))
463 (u8"username", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::username, _4)));
464
465 skip_whitespace = *(&qi::ascii::char_ >> qi::ascii::space);
466 header = skip_whitespace >> qi::ascii::no_case[u8"digest"] >> skip_whitespace;
467 quoted_string = (qi::lit(quote) >> qi::raw[+(u8"\\\"" | (qi::ascii::char_ - quote))] >> qi::lit(quote));
468 token =
469 (!qi::lit(quote) >> qi::raw[+(&qi::ascii::char_ >> (qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}")))]) |
470 quoted_string;
471 fields = field_table >> skip_whitespace >> equal_sign >> skip_whitespace;
472 }
473
474 boost::optional<auth_message> operator()(const boost::string_ref request) const
475 {
476 namespace qi = boost::spirit::qi;
477
478 iterator current = request.begin();
479 const iterator end = request.end();
480
481 if (!qi::parse(current, end, header))
482 {
483 return boost::none;
484 }
485
486 auth_message info{};
487 field_parser null_parser{};
488 std::reference_wrapper<const field_parser> field = null_parser;
489
490 do // require at least one field; require field after `,` character
491 {
492 if (!qi::parse(current, end, fields, field) || !field(*this, current, end, info))
493 {
494 return boost::none;
495 }
496 qi::parse(current, end, skip_whitespace);
497 } while (qi::parse(current, end, qi::char_(comma) >> skip_whitespace));
498 return boost::make_optional(current == end, info);
499 }
500
501 private:
502 boost::spirit::qi::symbols<
503 char, field_parser, boost::spirit::qi::tst<char, field_parser>, ascii_tolower_
504 > field_table;
505 boost::spirit::qi::rule<iterator> skip_whitespace;
506 boost::spirit::qi::rule<iterator> header;
507 boost::spirit::qi::rule<iterator, boost::iterator_range<iterator>()> quoted_string;
508 boost::spirit::qi::rule<iterator, boost::iterator_range<iterator>()> token;
509 boost::spirit::qi::rule<iterator, std::reference_wrapper<const field_parser>()> fields;
510 }; // parser
511
512 static const parser parse_;
513 return parse_(request);
514 }
515
516 struct has_valid_response
517 {
518 template<typename Digest, typename Result>
519 Result generate_old_response(Digest digest, const Result& key, const Result& auth) const
520 {
521 return digest(key, u8":", request.nonce, u8":", auth);
522 }
523
524 template<typename Digest, typename Result>
525 Result generate_new_response(Digest digest, const Result& key, const Result& auth) const
526 {
527 return digest(
528 key, u8":", request.nonce, u8":", request.nc, u8":", request.cnonce, u8":", request.qop, u8":", auth
529 );
530 }
531
532 template<typename Result>
533 bool check(const Result& result) const
534 {
535 return boost::equals(request.response, result, ascii_iequal);
536 }
537
538 template<typename Digest>
539 bool operator()(const Digest& digest) const
540 {
541 if (boost::starts_with(request.algorithm, Digest::name, ascii_iequal) ||
542 (request.algorithm.empty() && std::is_same<md5_, Digest>::value))
543 {
544 auto key = generate_a1(digest, user.credentials, auth_realm);
545 if (boost::ends_with(request.algorithm, sess_algo, ascii_iequal))
546 {
547 key = digest(key, u8":", request.nonce, u8":", request.cnonce);
548 }
549
550 auto auth = digest(method, u8":", request.uri);
551 if (request.qop.empty())
552 {
553 return check(generate_old_response(std::move(digest), std::move(key), std::move(auth)));
554 }
555 else if (boost::equals(ceref(u8"auth"), request.qop, ascii_iequal))
556 {
557 return check(generate_new_response(std::move(digest), std::move(key), std::move(auth)));
558 }
559 }
560 return false;
561 }
562
563 const auth_message& request;
564 const http::http_server_auth::session& user;
565 const boost::string_ref method;
566 };
567
568 boost::optional<std::uint32_t> counter() const
569 {
570 namespace qi = boost::spirit::qi;
571 using hex = qi::uint_parser<std::uint32_t, 16>;
572 std::uint32_t value = 0;
573 const bool converted = qi::parse(nc.begin(), nc.end(), hex{}, value);
574 return boost::make_optional(converted, value);
575 }
576
577 struct server_parameters
578 {
579 server_parameters()
580 : nonce(), opaque(), realm(), stale(), value_generator()
581 , index(boost::fusion::size(digest_algorithms))
582 {}
583
584 template<typename DigestIter>
585 explicit server_parameters(const auth_message& request, const DigestIter& digest)
586 : nonce(request.nonce)
587 , opaque(request.opaque)
588 , stale(request.stale)
589 , realm(request.realm)
590 , value_generator()
591 , index(boost::fusion::distance(boost::fusion::begin(digest_algorithms), digest))
592 {
593 using digest_type = typename boost::fusion::result_of::value_of<DigestIter>::type;
594
595 // debug check internal state of the auth_message class
596 assert(
597 (std::is_same<digest_type, md5_>::value) ||
598 boost::equals((*digest).name, request.algorithm, ascii_iequal)
599 );
600 if (request.qop.empty())
601 value_generator = old_algorithm<digest_type>{*digest};
602 else
603 {
604 for (auto elem = boost::make_split_iterator(request.qop, boost::token_finder(http_list_separator));
605 !elem.eof();
606 ++elem)
607 {
608 if (boost::equals(ceref(u8"auth"), *elem, ascii_iequal))
609 {
610 value_generator = auth_algorithm<digest_type>{*digest};
611 break;
612 }
613 }
614 if (!value_generator) // no supported qop mode
615 index = boost::fusion::size(digest_algorithms);
616 }
617 }
618
619 http::http_client_auth::session::keys take()
620 {
621 return {to_string(nonce), to_string(opaque), to_string(realm), std::move(value_generator)};
622 }
623
624 boost::iterator_range<iterator> nonce;
625 boost::iterator_range<iterator> opaque;
626 boost::iterator_range<iterator> realm;
627 boost::iterator_range<iterator> stale;
628 http::http_client_auth::session::keys::algorithm value_generator;
629 unsigned index;
630 };
631
632 struct matches_algorithm
633 {
634 template<typename DigestIter>
635 server_parameters operator()(server_parameters current, const DigestIter& digest) const
636 {
637 if (!current.value_generator)
638 {
639 if (boost::equals(response.algorithm, (*digest).name, ascii_iequal))
640 {
641 current = server_parameters{response, digest};
642 }
643 }
644 return current;
645 }
646 const auth_message& response;
647 };
648
649
650 boost::iterator_range<iterator> algorithm;
651 boost::iterator_range<iterator> cnonce;
652 boost::iterator_range<iterator> nc;
653 boost::iterator_range<iterator> nonce;
654 boost::iterator_range<iterator> opaque;
655 boost::iterator_range<iterator> qop;
656 boost::iterator_range<iterator> realm;
657 boost::iterator_range<iterator> response;
658 boost::iterator_range<iterator> stale;
659 boost::iterator_range<iterator> uri;
660 boost::iterator_range<iterator> username;
661 }; // auth_message
662
663 struct add_challenge
664 {
665 template<typename Digest>
666 void operator()(const Digest& digest) const
667 {
668 static constexpr const auto fvalue = ceref(u8"Digest qop=\"auth\"");
669
670 for (unsigned i = 0; i < 2; ++i)
671 {
672 std::string out(fvalue);
673
674 const auto algorithm = boost::range::join(
675 Digest::name, (i == 0 ? boost::string_ref{} : sess_algo)
676 );
677 add_field(out, u8"algorithm", algorithm);
678 add_field(out, u8"realm", quoted(auth_realm));
679 add_field(out, u8"nonce", quoted(nonce));
680 add_field(out, u8"stale", is_stale ? ceref("true") : ceref("false"));
681
682 fields.push_back(std::make_pair(std::string(server_auth_field), std::move(out)));
683 }
684 }
685
686 const boost::string_ref nonce;
687 std::list<std::pair<std::string, std::string>>& fields;
688 const bool is_stale;
689 };
690
691 http::http_response_info create_digest_response(const boost::string_ref nonce, const bool is_stale)
692 {
694 rc.m_response_code = 401;
695 rc.m_response_comment = u8"Unauthorized";
696 rc.m_mime_tipe = u8"text/html";
697 rc.m_body =
698 u8"<html><head><title>Unauthorized Access</title></head><body><h1>401 Unauthorized</h1></body></html>";
699
700 boost::fusion::for_each(
701 digest_algorithms, add_challenge{nonce, rc.m_additional_fields, is_stale}
702 );
703
704 return rc;
705 }
706}
707
708namespace epee
709{
710 namespace net_utils
711 {
712 namespace http
713 {
714 http_server_auth::http_server_auth(login credentials, std::function<void(size_t, uint8_t*)> r)
715 : user(session{std::move(credentials)}), rng(std::move(r)) {
716 }
717
718 boost::optional<http_response_info> http_server_auth::do_get_response(const http_request_info& request)
719 {
720 assert(user);
721 using field = std::pair<std::string, std::string>;
722
723 const std::list<field>& fields = request.m_header_info.m_etc_fields;
724 const auto auth = boost::find_if(fields, [] (const field& value) {
725 return boost::equals(client_auth_field, value.first, ascii_iequal);
726 });
727
728 bool is_stale = false;
729 if (auth != fields.end())
730 {
731 ++(user->counter);
732 switch (auth_message::verify(request.m_http_method_str, auth->second, *user))
733 {
734 case auth_message::kPass:
735 return boost::none;
736
737 case auth_message::kStale:
738 is_stale = true;
739 break;
740
741 default:
742 case auth_message::kFail:
743 break;
744 }
745 }
746 user->counter = 0;
747 {
748 std::array<std::uint8_t, 16> rand_128bit{{}};
749 rng(rand_128bit.size(), rand_128bit.data());
750 user->nonce = string_encoding::base64_encode(rand_128bit.data(), rand_128bit.size());
751 }
752 return create_digest_response(user->nonce, is_stale);
753 }
754
756 : user(session{std::move(credentials)}) {
757 }
758
759 http_client_auth::status http_client_auth::do_handle_401(const http_response_info& response)
760 {
761 assert(user);
762 const bool first_auth = (user->counter == 0);
763 user->server = auth_message::extract(response, first_auth);
764 if (user->server.generator)
765 {
766 user->counter = 0;
767 return kSuccess;
768 }
769 return first_auth ? kParseFailure : kBadPassword;
770 }
771
772 boost::optional<std::pair<std::string, std::string>> http_client_auth::do_get_auth_field(
773 const boost::string_ref method, const boost::string_ref uri)
774 {
775 assert(user);
776 if (user->server.generator)
777 {
778 ++(user->counter);
779 return std::make_pair(std::string(client_auth_field), user->server.generator(*user, method, uri));
780 }
781 return boost::none;
782 }
783 }
784 }
785}
786
unsigned char u8
const char * data() const noexcept
size_t size() const noexcept
const char * key
int bool
Definition hash.h:37
std::string hex(difficulty_type v)
epee::misc_utils::struct_init< response_t > response
std::string to_string(t_connection_type type)
std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len)
key zero()
Definition rctOps.h:70
STL namespace.
const T & move(const T &t)
const GenericPointer< typename T::ValueType > T2 value
Definition pointer.h:1225
const CharType(& source)[N]
Definition pointer.h:1147
#define extract(n)
CXA_THROW_INFO_T * info
unsigned char uint8_t
Definition stdint.h:124
static std::array< char, N *2 > array(const std::array< std::uint8_t, N > &src) noexcept
Definition hex.h:53
#define T(x)