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