94.44% Lines (85/90) 96.30% Functions (26/27)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/http 7   // Official repository: https://github.com/cppalliance/http
8   // 8   //
9   9  
10   /** @file 10   /** @file
11   bcrypt password hashing library. 11   bcrypt password hashing library.
12   12  
13   This header provides bcrypt password hashing with three API tiers: 13   This header provides bcrypt password hashing with three API tiers:
14   14  
15   **Tier 1 -- Synchronous** (low-level, no capy dependency): 15   **Tier 1 -- Synchronous** (low-level, no capy dependency):
16   @code 16   @code
17   bcrypt::result r = bcrypt::hash("password", 12); 17   bcrypt::result r = bcrypt::hash("password", 12);
18 - std::error_code ec; 18 + system::error_code ec;
19   bool ok = bcrypt::compare("password", r.str(), ec); 19   bool ok = bcrypt::compare("password", r.str(), ec);
20   @endcode 20   @endcode
21   21  
22   **Tier 2 -- Capy Task** (lazy coroutine, caller controls executor): 22   **Tier 2 -- Capy Task** (lazy coroutine, caller controls executor):
23   @code 23   @code
24   auto r = co_await bcrypt::hash_task("password", 12); 24   auto r = co_await bcrypt::hash_task("password", 12);
25   @endcode 25   @endcode
26   26  
27   **Tier 3 -- Friendly Async** (auto-offloads to system thread pool): 27   **Tier 3 -- Friendly Async** (auto-offloads to system thread pool):
28   @code 28   @code
29   auto r = co_await bcrypt::hash_async("password", 12); 29   auto r = co_await bcrypt::hash_async("password", 12);
30   bool ok = co_await bcrypt::compare_async("password", r.str()); 30   bool ok = co_await bcrypt::compare_async("password", r.str());
31   @endcode 31   @endcode
32   */ 32   */
33   33  
34   #ifndef BOOST_HTTP_BCRYPT_HPP 34   #ifndef BOOST_HTTP_BCRYPT_HPP
35   #define BOOST_HTTP_BCRYPT_HPP 35   #define BOOST_HTTP_BCRYPT_HPP
36   36  
37   #include <boost/http/detail/config.hpp> 37   #include <boost/http/detail/config.hpp>
38   #include <boost/http/detail/except.hpp> 38   #include <boost/http/detail/except.hpp>
39   #include <boost/core/detail/string_view.hpp> 39   #include <boost/core/detail/string_view.hpp>
  40 + #include <boost/system/error_category.hpp>
  41 + #include <boost/system/error_code.hpp>
  42 + #include <boost/system/is_error_code_enum.hpp>
40   43  
41   #include <boost/capy/continuation.hpp> 44   #include <boost/capy/continuation.hpp>
42   #include <boost/capy/task.hpp> 45   #include <boost/capy/task.hpp>
43   #include <boost/capy/ex/executor_ref.hpp> 46   #include <boost/capy/ex/executor_ref.hpp>
44   #include <boost/capy/ex/io_env.hpp> 47   #include <boost/capy/ex/io_env.hpp>
45   #include <boost/capy/ex/run_async.hpp> 48   #include <boost/capy/ex/run_async.hpp>
46   #include <boost/capy/ex/system_context.hpp> 49   #include <boost/capy/ex/system_context.hpp>
47   50  
48   #include <cstddef> 51   #include <cstddef>
49   #include <cstring> 52   #include <cstring>
50   #include <exception> 53   #include <exception>
51   #include <string> 54   #include <string>
52   #include <system_error> 55   #include <system_error>
53   56  
54   namespace boost { 57   namespace boost {
55   namespace http { 58   namespace http {
56   namespace bcrypt { 59   namespace bcrypt {
57   60  
58   //------------------------------------------------ 61   //------------------------------------------------
59   62  
60   /** bcrypt hash version prefix. 63   /** bcrypt hash version prefix.
61   64  
62   The version determines which variant of bcrypt is used. 65   The version determines which variant of bcrypt is used.
63   All versions produce compatible hashes. 66   All versions produce compatible hashes.
64   */ 67   */
65   enum class version 68   enum class version
66   { 69   {
67   /// $2a$ - Original specification 70   /// $2a$ - Original specification
68   v2a, 71   v2a,
69   72  
70   /// $2b$ - Fixed handling of passwords > 255 chars (recommended) 73   /// $2b$ - Fixed handling of passwords > 255 chars (recommended)
71   v2b 74   v2b
72   }; 75   };
73   76  
74   //------------------------------------------------ 77   //------------------------------------------------
75   78  
76   /** Error codes for bcrypt operations. 79   /** Error codes for bcrypt operations.
77   80  
78   These errors indicate malformed input from untrusted sources. 81   These errors indicate malformed input from untrusted sources.
79   */ 82   */
80   enum class error 83   enum class error
81   { 84   {
82   /// Success 85   /// Success
83   ok = 0, 86   ok = 0,
84   87  
85   /// Salt string is malformed 88   /// Salt string is malformed
86   invalid_salt, 89   invalid_salt,
87   90  
88   /// Hash string is malformed 91   /// Hash string is malformed
89   invalid_hash 92   invalid_hash
90   }; 93   };
91   94  
92   } // bcrypt 95   } // bcrypt
93   } // http 96   } // http
94   97  
  98 + namespace system {
  99 + template<>
  100 + struct is_error_code_enum<
  101 + ::boost::http::bcrypt::error>
  102 + {
  103 + static bool const value = true;
  104 + };
  105 + } // system
95   } // boost 106   } // boost
96   107  
97   namespace std { 108   namespace std {
98   template<> 109   template<>
99   struct is_error_code_enum< 110   struct is_error_code_enum<
100   ::boost::http::bcrypt::error> 111   ::boost::http::bcrypt::error>
101   : std::true_type {}; 112   : std::true_type {};
102   } // std 113   } // std
103   114  
104   namespace boost { 115   namespace boost {
105   namespace http { 116   namespace http {
106   namespace bcrypt { 117   namespace bcrypt {
107   118  
108   namespace detail { 119   namespace detail {
109   120  
110   struct BOOST_SYMBOL_VISIBLE 121   struct BOOST_SYMBOL_VISIBLE
111   error_cat_type 122   error_cat_type
112 - : std::error_category 123 + : system::error_category
113   { 124   {
114   BOOST_HTTP_DECL const char* name( 125   BOOST_HTTP_DECL const char* name(
115   ) const noexcept override; 126   ) const noexcept override;
116   BOOST_HTTP_DECL std::string message( 127   BOOST_HTTP_DECL std::string message(
117   int) const override; 128   int) const override;
118 - constexpr error_cat_type() noexcept = default; 129 + BOOST_HTTP_DECL char const* message(
  130 + int, char*, std::size_t
  131 + ) const noexcept override;
  132 + BOOST_SYSTEM_CONSTEXPR error_cat_type()
  133 + : error_category(0xbc8f2a4e7c193d56)
  134 + {
  135 + }
119   }; 136   };
120   137  
121   BOOST_HTTP_DECL extern 138   BOOST_HTTP_DECL extern
122   error_cat_type error_cat; 139   error_cat_type error_cat;
123   140  
124   } // detail 141   } // detail
125   142  
126   inline 143   inline
127 - std::error_code 144 + BOOST_SYSTEM_CONSTEXPR
  145 + system::error_code
HITCBC 128   17 make_error_code( 146   17 make_error_code(
129   error ev) noexcept 147   error ev) noexcept
130   { 148   {
ECB 131 - 17 return std::error_code{ 149 + return system::error_code{
132   static_cast<std::underlying_type< 150   static_cast<std::underlying_type<
133   error>::type>(ev), 151   error>::type>(ev),
HITCBC 134   17 detail::error_cat}; 152   17 detail::error_cat};
135   } 153   }
136   154  
137   //------------------------------------------------ 155   //------------------------------------------------
138   156  
139   /** Fixed-size buffer for bcrypt hash output. 157   /** Fixed-size buffer for bcrypt hash output.
140   158  
141   Stores a bcrypt hash string (max 60 chars) in an 159   Stores a bcrypt hash string (max 60 chars) in an
142   inline buffer with no heap allocation. 160   inline buffer with no heap allocation.
143   161  
144   @par Example 162   @par Example
145   @code 163   @code
146   bcrypt::result r = bcrypt::hash("password", 10); 164   bcrypt::result r = bcrypt::hash("password", 10);
147   core::string_view sv = r; // or r.str() 165   core::string_view sv = r; // or r.str()
148   std::cout << r.c_str(); // null-terminated 166   std::cout << r.c_str(); // null-terminated
149   @endcode 167   @endcode
150   */ 168   */
151   class result 169   class result
152   { 170   {
153   char buf_[61]; 171   char buf_[61];
154   unsigned char size_; 172   unsigned char size_;
155   173  
156   public: 174   public:
157   /** Default constructor. 175   /** Default constructor.
158   176  
159   Constructs an empty result. 177   Constructs an empty result.
160   */ 178   */
HITCBC 161   31 result() noexcept 179   31 result() noexcept
HITCBC 162   31 : size_(0) 180   31 : size_(0)
163   { 181   {
HITCBC 164   31 buf_[0] = '\0'; 182   31 buf_[0] = '\0';
HITCBC 165   31 } 183   31 }
166   184  
167   /** Return the hash as a string_view. 185   /** Return the hash as a string_view.
168   */ 186   */
169   core::string_view 187   core::string_view
HITCBC 170   30 str() const noexcept 188   30 str() const noexcept
171   { 189   {
HITCBC 172   30 return core::string_view(buf_, size_); 190   30 return core::string_view(buf_, size_);
173   } 191   }
174   192  
175   /** Implicit conversion to string_view. 193   /** Implicit conversion to string_view.
176   */ 194   */
177   operator core::string_view() const noexcept 195   operator core::string_view() const noexcept
178   { 196   {
179   return str(); 197   return str();
180   } 198   }
181   199  
182   /** Return null-terminated C string. 200   /** Return null-terminated C string.
183   */ 201   */
184   char const* 202   char const*
HITCBC 185   1 c_str() const noexcept 203   1 c_str() const noexcept
186   { 204   {
HITCBC 187   1 return buf_; 205   1 return buf_;
188   } 206   }
189   207  
190   /** Return pointer to data. 208   /** Return pointer to data.
191   */ 209   */
192   char const* 210   char const*
193   data() const noexcept 211   data() const noexcept
194   { 212   {
195   return buf_; 213   return buf_;
196   } 214   }
197   215  
198   /** Return size in bytes (excludes null terminator). 216   /** Return size in bytes (excludes null terminator).
199   */ 217   */
200   std::size_t 218   std::size_t
HITCBC 201   7 size() const noexcept 219   7 size() const noexcept
202   { 220   {
HITCBC 203   7 return size_; 221   7 return size_;
204   } 222   }
205   223  
206   /** Check if result is empty. 224   /** Check if result is empty.
207   */ 225   */
208   bool 226   bool
HITCBC 209   4 empty() const noexcept 227   4 empty() const noexcept
210   { 228   {
HITCBC 211   4 return size_ == 0; 229   4 return size_ == 0;
212   } 230   }
213   231  
214   /** Check if result contains valid data. 232   /** Check if result contains valid data.
215   */ 233   */
216   explicit 234   explicit
HITCBC 217   2 operator bool() const noexcept 235   2 operator bool() const noexcept
218   { 236   {
HITCBC 219   2 return size_ != 0; 237   2 return size_ != 0;
220   } 238   }
221   239  
222   private: 240   private:
223   friend BOOST_HTTP_DECL result gen_salt(unsigned, version); 241   friend BOOST_HTTP_DECL result gen_salt(unsigned, version);
224   friend BOOST_HTTP_DECL result hash(core::string_view, unsigned, version); 242   friend BOOST_HTTP_DECL result hash(core::string_view, unsigned, version);
225 - friend BOOST_HTTP_DECL result hash(core::string_view, core::string_view, std::error_code&); 243 + friend BOOST_HTTP_DECL result hash(core::string_view, core::string_view, system::error_code&);
226   244  
HITCBC 227   25 char* buf() noexcept { return buf_; } 245   25 char* buf() noexcept { return buf_; }
HITCBC 228   25 void set_size(unsigned char n) noexcept 246   25 void set_size(unsigned char n) noexcept
229   { 247   {
HITCBC 230   25 size_ = n; 248   25 size_ = n;
HITCBC 231   25 buf_[n] = '\0'; 249   25 buf_[n] = '\0';
HITCBC 232   25 } 250   25 }
233   }; 251   };
234   252  
235   //------------------------------------------------ 253   //------------------------------------------------
236   254  
237   /** Generate a random salt. 255   /** Generate a random salt.
238   256  
239   Creates a bcrypt salt string suitable for use with 257   Creates a bcrypt salt string suitable for use with
240   the hash() function. 258   the hash() function.
241   259  
242   @par Preconditions 260   @par Preconditions
243   @code 261   @code
244   rounds >= 4 && rounds <= 31 262   rounds >= 4 && rounds <= 31
245   @endcode 263   @endcode
246   264  
247   @par Exception Safety 265   @par Exception Safety
248   Strong guarantee. 266   Strong guarantee.
249   267  
250   @par Complexity 268   @par Complexity
251   Constant. 269   Constant.
252   270  
253   @param rounds Cost factor. Each increment doubles the work. 271   @param rounds Cost factor. Each increment doubles the work.
254   Default is 10, which takes approximately 100ms on modern hardware. 272   Default is 10, which takes approximately 100ms on modern hardware.
255   273  
256   @param ver Hash version to use. 274   @param ver Hash version to use.
257   275  
258   @return A 29-character salt string. 276   @return A 29-character salt string.
259   277  
260   @throws std::invalid_argument if rounds is out of range. 278   @throws std::invalid_argument if rounds is out of range.
261   @throws system_error on RNG failure. 279   @throws system_error on RNG failure.
262   */ 280   */
263   BOOST_HTTP_DECL 281   BOOST_HTTP_DECL
264   result 282   result
265   gen_salt( 283   gen_salt(
266   unsigned rounds = 10, 284   unsigned rounds = 10,
267   version ver = version::v2b); 285   version ver = version::v2b);
268   286  
269   /** Hash a password with auto-generated salt. 287   /** Hash a password with auto-generated salt.
270   288  
271   Generates a random salt and hashes the password. 289   Generates a random salt and hashes the password.
272   290  
273   @par Preconditions 291   @par Preconditions
274   @code 292   @code
275   rounds >= 4 && rounds <= 31 293   rounds >= 4 && rounds <= 31
276   @endcode 294   @endcode
277   295  
278   @par Exception Safety 296   @par Exception Safety
279   Strong guarantee. 297   Strong guarantee.
280   298  
281   @par Complexity 299   @par Complexity
282   O(2^rounds). 300   O(2^rounds).
283   301  
284   @param password The password to hash. Only the first 72 bytes 302   @param password The password to hash. Only the first 72 bytes
285   are used (bcrypt limitation). 303   are used (bcrypt limitation).
286   304  
287   @param rounds Cost factor. Each increment doubles the work. 305   @param rounds Cost factor. Each increment doubles the work.
288   306  
289   @param ver Hash version to use. 307   @param ver Hash version to use.
290   308  
291   @return A 60-character hash string. 309   @return A 60-character hash string.
292   310  
293   @throws std::invalid_argument if rounds is out of range. 311   @throws std::invalid_argument if rounds is out of range.
294   @throws system_error on RNG failure. 312   @throws system_error on RNG failure.
295   */ 313   */
296   BOOST_HTTP_DECL 314   BOOST_HTTP_DECL
297   result 315   result
298   hash( 316   hash(
299   core::string_view password, 317   core::string_view password,
300   unsigned rounds = 10, 318   unsigned rounds = 10,
301   version ver = version::v2b); 319   version ver = version::v2b);
302   320  
303   /** Hash a password using a provided salt. 321   /** Hash a password using a provided salt.
304   322  
305   Uses the given salt to hash the password. The salt should 323   Uses the given salt to hash the password. The salt should
306   be a string previously returned by gen_salt() or extracted 324   be a string previously returned by gen_salt() or extracted
307   from a hash string. 325   from a hash string.
308   326  
309   @par Exception Safety 327   @par Exception Safety
310   Strong guarantee. 328   Strong guarantee.
311   329  
312   @par Complexity 330   @par Complexity
313   O(2^rounds). 331   O(2^rounds).
314   332  
315   @param password The password to hash. 333   @param password The password to hash.
316   334  
317   @param salt The salt string (29 characters). 335   @param salt The salt string (29 characters).
318   336  
319   @param ec Set to bcrypt::error::invalid_salt if the salt 337   @param ec Set to bcrypt::error::invalid_salt if the salt
320   is malformed. 338   is malformed.
321   339  
322   @return A 60-character hash string, or empty result on error. 340   @return A 60-character hash string, or empty result on error.
323   */ 341   */
324   BOOST_HTTP_DECL 342   BOOST_HTTP_DECL
325   result 343   result
326   hash( 344   hash(
327   core::string_view password, 345   core::string_view password,
328   core::string_view salt, 346   core::string_view salt,
329 - std::error_code& ec); 347 + system::error_code& ec);
330   348  
331   /** Compare a password against a hash. 349   /** Compare a password against a hash.
332   350  
333   Extracts the salt from the hash, re-hashes the password, 351   Extracts the salt from the hash, re-hashes the password,
334   and compares the result. 352   and compares the result.
335   353  
336   @par Exception Safety 354   @par Exception Safety
337   Strong guarantee. 355   Strong guarantee.
338   356  
339   @par Complexity 357   @par Complexity
340   O(2^rounds). 358   O(2^rounds).
341   359  
342   @param password The plaintext password to check. 360   @param password The plaintext password to check.
343   361  
344   @param hash The hash string to compare against. 362   @param hash The hash string to compare against.
345   363  
346   @param ec Set to bcrypt::error::invalid_hash if the hash 364   @param ec Set to bcrypt::error::invalid_hash if the hash
347   is malformed. 365   is malformed.
348   366  
349   @return true if the password matches the hash, false if 367   @return true if the password matches the hash, false if
350   it does not match OR if an error occurred. Always check 368   it does not match OR if an error occurred. Always check
351   ec to distinguish between a mismatch and an error. 369   ec to distinguish between a mismatch and an error.
352   */ 370   */
353   BOOST_HTTP_DECL 371   BOOST_HTTP_DECL
354   bool 372   bool
355   compare( 373   compare(
356   core::string_view password, 374   core::string_view password,
357   core::string_view hash, 375   core::string_view hash,
358 - std::error_code& ec); 376 + system::error_code& ec);
359   377  
360   /** Extract the cost factor from a hash string. 378   /** Extract the cost factor from a hash string.
361   379  
362   @par Exception Safety 380   @par Exception Safety
363   Strong guarantee. 381   Strong guarantee.
364   382  
365   @par Complexity 383   @par Complexity
366   Constant. 384   Constant.
367   385  
368   @param hash The hash string to parse. 386   @param hash The hash string to parse.
369   387  
370   @param ec Set to bcrypt::error::invalid_hash if the hash 388   @param ec Set to bcrypt::error::invalid_hash if the hash
371   is malformed. 389   is malformed.
372   390  
373   @return The cost factor (4-31) on success, or 0 if an 391   @return The cost factor (4-31) on success, or 0 if an
374   error occurred. 392   error occurred.
375   */ 393   */
376   BOOST_HTTP_DECL 394   BOOST_HTTP_DECL
377   unsigned 395   unsigned
378   get_rounds( 396   get_rounds(
379   core::string_view hash, 397   core::string_view hash,
380 - std::error_code& ec); 398 + system::error_code& ec);
381   399  
382   namespace detail { 400   namespace detail {
383   401  
384   // bcrypt truncates passwords to 72 bytes 402   // bcrypt truncates passwords to 72 bytes
385   struct password_buf 403   struct password_buf
386   { 404   {
387   char data_[72]; 405   char data_[72];
388   unsigned char size_; 406   unsigned char size_;
389   407  
HITCBC 390   14 explicit password_buf( 408   14 explicit password_buf(
391   core::string_view s) noexcept 409   core::string_view s) noexcept
HITCBC 392   28 : size_(static_cast<unsigned char>( 410   28 : size_(static_cast<unsigned char>(
HITCBC 393   14 (std::min)(s.size(), std::size_t{72}))) 411   14 (std::min)(s.size(), std::size_t{72})))
394   { 412   {
HITCBC 395   14 std::memcpy(data_, s.data(), size_); 413   14 std::memcpy(data_, s.data(), size_);
HITCBC 396   14 } 414   14 }
397   415  
HITCBC 398   14 operator core::string_view() const noexcept 416   14 operator core::string_view() const noexcept
399   { 417   {
HITCBC 400   14 return {data_, size_}; 418   14 return {data_, size_};
401   } 419   }
402   }; 420   };
403   421  
404   // bcrypt hashes are always 60 characters 422   // bcrypt hashes are always 60 characters
405   struct hash_buf 423   struct hash_buf
406   { 424   {
407   char data_[61]; 425   char data_[61];
408   unsigned char size_; 426   unsigned char size_;
409   427  
HITCBC 410   9 explicit hash_buf( 428   9 explicit hash_buf(
411   core::string_view s) noexcept 429   core::string_view s) noexcept
HITCBC 412   18 : size_(static_cast<unsigned char>( 430   18 : size_(static_cast<unsigned char>(
HITCBC 413   9 (std::min)(s.size(), std::size_t{60}))) 431   9 (std::min)(s.size(), std::size_t{60})))
414   { 432   {
HITCBC 415   9 std::memcpy(data_, s.data(), size_); 433   9 std::memcpy(data_, s.data(), size_);
HITCBC 416   9 data_[size_] = '\0'; 434   9 data_[size_] = '\0';
HITCBC 417   9 } 435   9 }
418   436  
HITCBC 419   9 operator core::string_view() const noexcept 437   9 operator core::string_view() const noexcept
420   { 438   {
HITCBC 421   9 return {data_, size_}; 439   9 return {data_, size_};
422   } 440   }
423   }; 441   };
424   442  
425   } // detail 443   } // detail
426   444  
427   //------------------------------------------------ 445   //------------------------------------------------
428   446  
429   /** Hash a password, returning a lazy task. 447   /** Hash a password, returning a lazy task.
430   448  
431   Returns a @ref capy::task that wraps the synchronous 449   Returns a @ref capy::task that wraps the synchronous
432   hash() call. The caller can co_await this task directly 450   hash() call. The caller can co_await this task directly
433   or launch it on a specific executor via run_async(). 451   or launch it on a specific executor via run_async().
434   452  
435   @par Example 453   @par Example
436   @code 454   @code
437   // co_await in current context 455   // co_await in current context
438   bcrypt::result r = co_await bcrypt::hash_task("password", 12); 456   bcrypt::result r = co_await bcrypt::hash_task("password", 12);
439   457  
440   // or launch on a specific executor 458   // or launch on a specific executor
441   run_async(my_executor)(bcrypt::hash_task("password", 12)); 459   run_async(my_executor)(bcrypt::hash_task("password", 12));
442   @endcode 460   @endcode
443   461  
444   @param password The password to hash. 462   @param password The password to hash.
445   463  
446   @param rounds Cost factor. Each increment doubles the work. 464   @param rounds Cost factor. Each increment doubles the work.
447   465  
448   @param ver Hash version to use. 466   @param ver Hash version to use.
449   467  
450   @return A lazy task yielding `result`. 468   @return A lazy task yielding `result`.
451   469  
452   @throws std::invalid_argument if rounds is out of range. 470   @throws std::invalid_argument if rounds is out of range.
453   @throws system_error on RNG failure. 471   @throws system_error on RNG failure.
454   */ 472   */
455   inline 473   inline
456   capy::task<result> 474   capy::task<result>
HITCBC 457   4 hash_task( 475   4 hash_task(
458   core::string_view password, 476   core::string_view password,
459   unsigned rounds = 10, 477   unsigned rounds = 10,
460   version ver = version::v2b) 478   version ver = version::v2b)
461   { 479   {
462   detail::password_buf pw(password); 480   detail::password_buf pw(password);
463   co_return hash(pw, rounds, ver); 481   co_return hash(pw, rounds, ver);
HITCBC 464   8 } 482   8 }
465   483  
466   /** Compare a password against a hash, returning a lazy task. 484   /** Compare a password against a hash, returning a lazy task.
467   485  
468   Returns a @ref capy::task that wraps the synchronous 486   Returns a @ref capy::task that wraps the synchronous
469   compare() call. Errors are translated to exceptions. 487   compare() call. Errors are translated to exceptions.
470   488  
471   @par Example 489   @par Example
472   @code 490   @code
473   bool ok = co_await bcrypt::compare_task("password", stored_hash); 491   bool ok = co_await bcrypt::compare_task("password", stored_hash);
474   @endcode 492   @endcode
475   493  
476   @param password The plaintext password to check. 494   @param password The plaintext password to check.
477   495  
478   @param hash_str The hash string to compare against. 496   @param hash_str The hash string to compare against.
479   497  
480   @return A lazy task yielding `bool`. 498   @return A lazy task yielding `bool`.
481   499  
482   @throws system_error if the hash is malformed. 500   @throws system_error if the hash is malformed.
483   */ 501   */
484   inline 502   inline
485   capy::task<bool> 503   capy::task<bool>
HITCBC 486   6 compare_task( 504   6 compare_task(
487   core::string_view password, 505   core::string_view password,
488   core::string_view hash_str) 506   core::string_view hash_str)
489   { 507   {
490   detail::password_buf pw(password); 508   detail::password_buf pw(password);
491   detail::hash_buf hs(hash_str); 509   detail::hash_buf hs(hash_str);
492 - std::error_code ec; 510 + system::error_code ec;
493   bool ok = compare(pw, hs, ec); 511   bool ok = compare(pw, hs, ec);
494 - if(ec) 512 + if(ec.failed())
495   http::detail::throw_system_error(ec); 513   http::detail::throw_system_error(ec);
496   co_return ok; 514   co_return ok;
HITCBC 497   12 } 515   12 }
498   516  
499   //------------------------------------------------ 517   //------------------------------------------------
500   518  
501   namespace detail { 519   namespace detail {
502   520  
503   struct hash_async_op 521   struct hash_async_op
504   { 522   {
505   password_buf password_; 523   password_buf password_;
506   unsigned rounds_; 524   unsigned rounds_;
507   version ver_; 525   version ver_;
508   result result_; 526   result result_;
509   std::exception_ptr ep_; 527   std::exception_ptr ep_;
510   capy::continuation cont_; 528   capy::continuation cont_;
511   529  
HITCBC 512   1 bool await_ready() const noexcept 530   1 bool await_ready() const noexcept
513   { 531   {
HITCBC 514   1 return false; 532   1 return false;
515   } 533   }
516   534  
HITCBC 517   1 void await_suspend( 535   1 void await_suspend(
518   std::coroutine_handle<void> cont, 536   std::coroutine_handle<void> cont,
519   capy::io_env const* env) 537   capy::io_env const* env)
520   { 538   {
HITCBC 521   1 cont_.h = cont; 539   1 cont_.h = cont;
HITCBC 522   1 auto caller_ex = env->executor; 540   1 auto caller_ex = env->executor;
HITCBC 523   1 auto& pool = capy::get_system_context(); 541   1 auto& pool = capy::get_system_context();
HITCBC 524   1 auto sys_ex = pool.get_executor(); 542   1 auto sys_ex = pool.get_executor();
HITCBC 525   1 capy::run_async(sys_ex, 543   1 capy::run_async(sys_ex,
HITCBC 526   1 [this, caller_ex] 544   1 [this, caller_ex]
527   (result r) mutable 545   (result r) mutable
528   { 546   {
HITCBC 529   1 result_ = r; 547   1 result_ = r;
HITCBC 530   1 caller_ex.dispatch(cont_).resume(); 548   1 caller_ex.dispatch(cont_).resume();
HITCBC 531   1 }, 549   1 },
MISUBC 532   ✗ [this, caller_ex] 550   ✗ [this, caller_ex]
533   (std::exception_ptr ep) mutable 551   (std::exception_ptr ep) mutable
534   { 552   {
MISUBC 535   ✗ ep_ = ep; 553   ✗ ep_ = ep;
MISUBC 536   ✗ caller_ex.dispatch(cont_).resume(); 554   ✗ caller_ex.dispatch(cont_).resume();
MISUBC 537   ✗ } 555   ✗ }
HITCBC 538   1 )(hash_task(password_, rounds_, ver_)); 556   1 )(hash_task(password_, rounds_, ver_));
HITCBC 539   1 } 557   1 }
540   558  
HITCBC 541   1 result await_resume() 559   1 result await_resume()
542   { 560   {
HITCBC 543   1 if(ep_) 561   1 if(ep_)
MISUBC 544   ✗ std::rethrow_exception(ep_); 562   ✗ std::rethrow_exception(ep_);
HITCBC 545   1 return result_; 563   1 return result_;
546   } 564   }
547   }; 565   };
548   566  
549   struct compare_async_op 567   struct compare_async_op
550   { 568   {
551   password_buf password_; 569   password_buf password_;
552   hash_buf hash_str_; 570   hash_buf hash_str_;
553   bool result_ = false; 571   bool result_ = false;
554   std::exception_ptr ep_; 572   std::exception_ptr ep_;
555   capy::continuation cont_; 573   capy::continuation cont_;
556   574  
HITCBC 557   3 bool await_ready() const noexcept 575   3 bool await_ready() const noexcept
558   { 576   {
HITCBC 559   3 return false; 577   3 return false;
560   } 578   }
561   579  
HITCBC 562   3 void await_suspend( 580   3 void await_suspend(
563   std::coroutine_handle<void> cont, 581   std::coroutine_handle<void> cont,
564   capy::io_env const* env) 582   capy::io_env const* env)
565   { 583   {
HITCBC 566   3 cont_.h = cont; 584   3 cont_.h = cont;
HITCBC 567   3 auto caller_ex = env->executor; 585   3 auto caller_ex = env->executor;
HITCBC 568   3 auto& pool = capy::get_system_context(); 586   3 auto& pool = capy::get_system_context();
HITCBC 569   3 auto sys_ex = pool.get_executor(); 587   3 auto sys_ex = pool.get_executor();
HITCBC 570   3 capy::run_async(sys_ex, 588   3 capy::run_async(sys_ex,
HITCBC 571   2 [this, caller_ex] 589   2 [this, caller_ex]
572   (bool ok) mutable 590   (bool ok) mutable
573   { 591   {
HITCBC 574   2 result_ = ok; 592   2 result_ = ok;
HITCBC 575   2 caller_ex.dispatch(cont_).resume(); 593   2 caller_ex.dispatch(cont_).resume();
HITCBC 576   2 }, 594   2 },
HITCBC 577   1 [this, caller_ex] 595   1 [this, caller_ex]
578   (std::exception_ptr ep) mutable 596   (std::exception_ptr ep) mutable
579   { 597   {
HITCBC 580   1 ep_ = ep; 598   1 ep_ = ep;
HITCBC 581   1 caller_ex.dispatch(cont_).resume(); 599   1 caller_ex.dispatch(cont_).resume();
HITCBC 582   1 } 600   1 }
HITCBC 583   3 )(compare_task(password_, hash_str_)); 601   3 )(compare_task(password_, hash_str_));
HITCBC 584   3 } 602   3 }
585   603  
HITCBC 586   3 bool await_resume() 604   3 bool await_resume()
587   { 605   {
HITCBC 588   3 if(ep_) 606   3 if(ep_)
HITCBC 589   1 std::rethrow_exception(ep_); 607   1 std::rethrow_exception(ep_);
HITCBC 590   2 return result_; 608   2 return result_;
591   } 609   }
592   }; 610   };
593   611  
594   } // detail 612   } // detail
595   613  
596   /** Hash a password asynchronously on the system thread pool. 614   /** Hash a password asynchronously on the system thread pool.
597   615  
598   Returns an awaitable that offloads the CPU-intensive 616   Returns an awaitable that offloads the CPU-intensive
599   bcrypt work to the system thread pool, then resumes 617   bcrypt work to the system thread pool, then resumes
600   the caller on their original executor. Modeled after 618   the caller on their original executor. Modeled after
601   Express.js: `await bcrypt.hash(password, 12)`. 619   Express.js: `await bcrypt.hash(password, 12)`.
602   620  
603   @par Example 621   @par Example
604   @code 622   @code
605   bcrypt::result r = co_await bcrypt::hash_async("my_password", 12); 623   bcrypt::result r = co_await bcrypt::hash_async("my_password", 12);
606   @endcode 624   @endcode
607   625  
608   @param password The password to hash. 626   @param password The password to hash.
609   627  
610   @param rounds Cost factor. Each increment doubles the work. 628   @param rounds Cost factor. Each increment doubles the work.
611   629  
612   @param ver Hash version to use. 630   @param ver Hash version to use.
613   631  
614   @return An awaitable yielding `result`. 632   @return An awaitable yielding `result`.
615   633  
616   @throws std::invalid_argument if rounds is out of range. 634   @throws std::invalid_argument if rounds is out of range.
617   @throws system_error on RNG failure. 635   @throws system_error on RNG failure.
618   */ 636   */
619   inline 637   inline
620   detail::hash_async_op 638   detail::hash_async_op
HITCBC 621   1 hash_async( 639   1 hash_async(
622   core::string_view password, 640   core::string_view password,
623   unsigned rounds = 10, 641   unsigned rounds = 10,
624   version ver = version::v2b) 642   version ver = version::v2b)
625   { 643   {
HITCBC 626   1 return detail::hash_async_op{ 644   1 return detail::hash_async_op{
627   detail::password_buf(password), 645   detail::password_buf(password),
628   rounds, 646   rounds,
629   ver, 647   ver,
630   {}, 648   {},
631   {}, 649   {},
HITCBC 632   1 {}}; 650   1 {}};
633   } 651   }
634   652  
635   /** Compare a password against a hash asynchronously. 653   /** Compare a password against a hash asynchronously.
636   654  
637   Returns an awaitable that offloads the CPU-intensive 655   Returns an awaitable that offloads the CPU-intensive
638   bcrypt work to the system thread pool, then resumes 656   bcrypt work to the system thread pool, then resumes
639   the caller on their original executor. Modeled after 657   the caller on their original executor. Modeled after
640   Express.js: `await bcrypt.compare(password, hash)`. 658   Express.js: `await bcrypt.compare(password, hash)`.
641   659  
642   @par Example 660   @par Example
643   @code 661   @code
644   bool ok = co_await bcrypt::compare_async("my_password", stored_hash); 662   bool ok = co_await bcrypt::compare_async("my_password", stored_hash);
645   @endcode 663   @endcode
646   664  
647   @param password The plaintext password to check. 665   @param password The plaintext password to check.
648   666  
649   @param hash_str The hash string to compare against. 667   @param hash_str The hash string to compare against.
650   668  
651   @return An awaitable yielding `bool`. 669   @return An awaitable yielding `bool`.
652   670  
653   @throws system_error if the hash is malformed. 671   @throws system_error if the hash is malformed.
654   */ 672   */
655   inline 673   inline
656   detail::compare_async_op 674   detail::compare_async_op
HITCBC 657   3 compare_async( 675   3 compare_async(
658   core::string_view password, 676   core::string_view password,
659   core::string_view hash_str) 677   core::string_view hash_str)
660   { 678   {
HITCBC 661   3 return detail::compare_async_op{ 679   3 return detail::compare_async_op{
662   detail::password_buf(password), 680   detail::password_buf(password),
663   detail::hash_buf(hash_str), 681   detail::hash_buf(hash_str),
664   false, 682   false,
665   {}, 683   {},
HITCBC 666   3 {}}; 684   3 {}};
667   } 685   }
668   686  
669   } // bcrypt 687   } // bcrypt
670   } // http 688   } // http
671   } // boost 689   } // boost
672   690  
673   #endif 691   #endif