Electroneum
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
75 value. This is for maximum portability - C++ does not actually specify ASCII
76 as the encoding type for unprefixed string literals, etc. Although rare, the
77 effort required to support rare compiler encoding types is low.
78 
79 Also be careful of qi::ascii character classes (`qi::asci::alpha`, etc.) -
80 non-ASCII characters will cause undefined behavior in the table lookup until
81 boost 1.60. The expression `&qi::ascii::char_` will fail on non-ASCII
82 characters without "consuming" the input character. */
83 
84 namespace
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) &&
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(
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 
708 namespace 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 
std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len)
const uint32_t T[512]
const CharType(& source)[N]
Definition: pointer.h:1147
size_t size() const noexcept
::std::string string
Definition: gtest-port.h:1097
epee::misc_utils::struct_init< response_t > response
const char * key
Definition: hmac_keccak.cpp:39
static std::array< char, N *2 > array(const std::array< std::uint8_t, N > &src) noexcept
Definition: hex.h:53
STL namespace.
unsigned char uint8_t
Definition: stdint.h:124
const char * name
void copy(key &AA, const key &A)
Definition: rctOps.h:79
unsigned int uint32_t
Definition: stdint.h:126
wipeable_string password
Definition: http_auth.h:57
CXA_THROW_INFO_T * info
Definition: stack_trace.cpp:91
#define extract(n)
const T & move(const T &t)
Definition: gtest-port.h:1317
const GenericPointer< typename T::ValueType > T2 value
Definition: pointer.h:1225
std::string to_string(t_connection_type type)
std::string hex(difficulty_type v)
Definition: difficulty.cpp:254
const char * data() const noexcept
key zero()
Definition: rctOps.h:70
unsigned char u8
Definition: chacha_private.h:9