77.42% Lines (48/62) 100.00% Functions (5/5)
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   #include <boost/http/bcrypt.hpp> 10   #include <boost/http/bcrypt.hpp>
11   #include <boost/http/detail/except.hpp> 11   #include <boost/http/detail/except.hpp>
12   #include "base64.hpp" 12   #include "base64.hpp"
13   #include "crypt.hpp" 13   #include "crypt.hpp"
14   14  
15   namespace boost { 15   namespace boost {
16   namespace http { 16   namespace http {
17   namespace bcrypt { 17   namespace bcrypt {
18   18  
19   result 19   result
HITCBC 20   7 gen_salt( 20   7 gen_salt(
21   unsigned rounds, 21   unsigned rounds,
22   version ver) 22   version ver)
23   { 23   {
24   // Validate preconditions 24   // Validate preconditions
HITCBC 25   7 if (rounds < 4 || rounds > 31) 25   7 if (rounds < 4 || rounds > 31)
MISUBC 26   http::detail::throw_invalid_argument("bcrypt rounds must be 4-31"); 26   http::detail::throw_invalid_argument("bcrypt rounds must be 4-31");
27   27  
28   // Generate random salt 28   // Generate random salt
29   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN]; 29   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
HITCBC 30   7 detail::generate_salt_bytes(salt_bytes); 30   7 detail::generate_salt_bytes(salt_bytes);
31   31  
32   // Format salt string 32   // Format salt string
HITCBC 33   7 result r; 33   7 result r;
HITCBC 34   7 std::size_t len = detail::format_salt( 34   7 std::size_t len = detail::format_salt(
35   r.buf(), 35   r.buf(),
36   salt_bytes, 36   salt_bytes,
37   rounds, 37   rounds,
38   ver); 38   ver);
39   39  
HITCBC 40   7 r.set_size(static_cast<unsigned char>(len)); 40   7 r.set_size(static_cast<unsigned char>(len));
HITCBC 41   14 return r; 41   14 return r;
42   } 42   }
43   43  
44   result 44   result
HITCBC 45   13 hash( 45   13 hash(
46   core::string_view password, 46   core::string_view password,
47   unsigned rounds, 47   unsigned rounds,
48   version ver) 48   version ver)
49   { 49   {
50   // Validate preconditions 50   // Validate preconditions
HITCBC 51   13 if (rounds < 4 || rounds > 31) 51   13 if (rounds < 4 || rounds > 31)
MISUBC 52   http::detail::throw_invalid_argument("bcrypt rounds must be 4-31"); 52   http::detail::throw_invalid_argument("bcrypt rounds must be 4-31");
53   53  
54   // Generate random salt 54   // Generate random salt
55   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN]; 55   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
HITCBC 56   13 detail::generate_salt_bytes(salt_bytes); 56   13 detail::generate_salt_bytes(salt_bytes);
57   57  
58   // Hash password 58   // Hash password
59   std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN]; 59   std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN];
HITCBC 60   13 detail::bcrypt_hash( 60   13 detail::bcrypt_hash(
61   password.data(), 61   password.data(),
62   password.size(), 62   password.size(),
63   salt_bytes, 63   salt_bytes,
64   rounds, 64   rounds,
65   hash_bytes); 65   hash_bytes);
66   66  
67   // Format output 67   // Format output
HITCBC 68   13 result r; 68   13 result r;
HITCBC 69   13 std::size_t len = detail::format_hash( 69   13 std::size_t len = detail::format_hash(
70   r.buf(), 70   r.buf(),
71   salt_bytes, 71   salt_bytes,
72   hash_bytes, 72   hash_bytes,
73   rounds, 73   rounds,
74   ver); 74   ver);
75   75  
HITCBC 76   13 r.set_size(static_cast<unsigned char>(len)); 76   13 r.set_size(static_cast<unsigned char>(len));
HITCBC 77   26 return r; 77   26 return r;
78   } 78   }
79   79  
80   result 80   result
HITCBC 81   7 hash( 81   7 hash(
82   core::string_view password, 82   core::string_view password,
83   core::string_view salt, 83   core::string_view salt,
84   system::error_code& ec) 84   system::error_code& ec)
85   { 85   {
HITCBC 86   7 ec = {}; 86   7 ec = {};
87   87  
88   // Parse salt 88   // Parse salt
89   version ver; 89   version ver;
90   unsigned rounds; 90   unsigned rounds;
91   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN]; 91   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
92   92  
HITCBC 93   7 if (!detail::parse_salt(salt, ver, rounds, salt_bytes)) 93   7 if (!detail::parse_salt(salt, ver, rounds, salt_bytes))
94   { 94   {
HITCBC 95   2 ec = make_error_code(error::invalid_salt); 95   2 ec = make_error_code(error::invalid_salt);
HITCBC 96   2 return result{}; 96   2 return result{};
97   } 97   }
98   98  
99   // Hash password 99   // Hash password
100   std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN]; 100   std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN];
HITCBC 101   5 detail::bcrypt_hash( 101   5 detail::bcrypt_hash(
102   password.data(), 102   password.data(),
103   password.size(), 103   password.size(),
104   salt_bytes, 104   salt_bytes,
105   rounds, 105   rounds,
106   hash_bytes); 106   hash_bytes);
107   107  
108   // Format output 108   // Format output
HITCBC 109   5 result r; 109   5 result r;
HITCBC 110   5 std::size_t len = detail::format_hash( 110   5 std::size_t len = detail::format_hash(
111   r.buf(), 111   r.buf(),
112   salt_bytes, 112   salt_bytes,
113   hash_bytes, 113   hash_bytes,
114   rounds, 114   rounds,
115   ver); 115   ver);
116   116  
HITCBC 117   5 r.set_size(static_cast<unsigned char>(len)); 117   5 r.set_size(static_cast<unsigned char>(len));
HITCBC 118   5 return r; 118   5 return r;
119   } 119   }
120   120  
121   bool 121   bool
HITCBC 122   14 compare( 122   14 compare(
123   core::string_view password, 123   core::string_view password,
124   core::string_view hash_str, 124   core::string_view hash_str,
125   system::error_code& ec) 125   system::error_code& ec)
126   { 126   {
HITCBC 127   14 ec = {}; 127   14 ec = {};
128   128  
129   // Parse hash to extract salt 129   // Parse hash to extract salt
130   version ver; 130   version ver;
131   unsigned rounds; 131   unsigned rounds;
132   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN]; 132   std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
133   133  
HITCBC 134   14 if (!detail::parse_salt(hash_str, ver, rounds, salt_bytes)) 134   14 if (!detail::parse_salt(hash_str, ver, rounds, salt_bytes))
135   { 135   {
HITCBC 136   4 ec = make_error_code(error::invalid_hash); 136   4 ec = make_error_code(error::invalid_hash);
HITCBC 137   4 return false; 137   4 return false;
138   } 138   }
139   139  
140   // Validate hash length 140   // Validate hash length
HITCBC 141   10 if (hash_str.size() != detail::BCRYPT_HASH_OUTPUT_LEN) 141   10 if (hash_str.size() != detail::BCRYPT_HASH_OUTPUT_LEN)
142   { 142   {
MISUBC 143   ec = make_error_code(error::invalid_hash); 143   ec = make_error_code(error::invalid_hash);
MISUBC 144   return false; 144   return false;
145   } 145   }
146   146  
147   // Decode stored hash (31 base64 chars starting at position 29) 147   // Decode stored hash (31 base64 chars starting at position 29)
148   std::uint8_t stored_hash[detail::BCRYPT_HASH_LEN]; 148   std::uint8_t stored_hash[detail::BCRYPT_HASH_LEN];
HITCBC 149   10 int decoded = detail::base64_decode( 149   10 int decoded = detail::base64_decode(
150   stored_hash, 150   stored_hash,
HITCBC 151   10 hash_str.data() + 29, 151   10 hash_str.data() + 29,
152   31); 152   31);
153   153  
HITCBC 154   10 if (decoded < 0) 154   10 if (decoded < 0)
155   { 155   {
MISUBC 156   ec = make_error_code(error::invalid_hash); 156   ec = make_error_code(error::invalid_hash);
MISUBC 157   return false; 157   return false;
158   } 158   }
159   159  
160   // Compute hash of provided password 160   // Compute hash of provided password
161   std::uint8_t computed_hash[detail::BCRYPT_HASH_LEN]; 161   std::uint8_t computed_hash[detail::BCRYPT_HASH_LEN];
HITCBC 162   10 detail::bcrypt_hash( 162   10 detail::bcrypt_hash(
163   password.data(), 163   password.data(),
164   password.size(), 164   password.size(),
165   salt_bytes, 165   salt_bytes,
166   rounds, 166   rounds,
167   computed_hash); 167   computed_hash);
168   168  
169   // Constant-time comparison (only first 23 bytes are used) 169   // Constant-time comparison (only first 23 bytes are used)
HITCBC 170   10 return detail::secure_compare(stored_hash, computed_hash, 23); 170   10 return detail::secure_compare(stored_hash, computed_hash, 23);
171   } 171   }
172   172  
173   unsigned 173   unsigned
HITCBC 174   4 get_rounds( 174   4 get_rounds(
175   core::string_view hash_str, 175   core::string_view hash_str,
176   system::error_code& ec) 176   system::error_code& ec)
177   { 177   {
HITCBC 178   4 ec = {}; 178   4 ec = {};
179   179  
180   // Minimum length check 180   // Minimum length check
HITCBC 181   4 if (hash_str.size() < 7) 181   4 if (hash_str.size() < 7)
182   { 182   {
MISUBC 183   ec = make_error_code(error::invalid_hash); 183   ec = make_error_code(error::invalid_hash);
MISUBC 184   return 0; 184   return 0;
185   } 185   }
186   186  
HITCBC 187   4 char const* s = hash_str.data(); 187   4 char const* s = hash_str.data();
188   188  
189   // Check prefix 189   // Check prefix
HITCBC 190   4 if (s[0] != '$' || s[1] != '2') 190   4 if (s[0] != '$' || s[1] != '2')
191   { 191   {
HITCBC 192   2 ec = make_error_code(error::invalid_hash); 192   2 ec = make_error_code(error::invalid_hash);
HITCBC 193   2 return 0; 193   2 return 0;
194   } 194   }
195   195  
196   // Check version character 196   // Check version character
HITCBC 197   2 if ((s[2] != 'a' && s[2] != 'b' && s[2] != 'y') || s[3] != '$') 197   2 if ((s[2] != 'a' && s[2] != 'b' && s[2] != 'y') || s[3] != '$')
198   { 198   {
MISUBC 199   ec = make_error_code(error::invalid_hash); 199   ec = make_error_code(error::invalid_hash);
MISUBC 200   return 0; 200   return 0;
201   } 201   }
202   202  
203   // Parse rounds 203   // Parse rounds
HITCBC 204   2 if (s[4] < '0' || s[4] > '9' || s[5] < '0' || s[5] > '9') 204   2 if (s[4] < '0' || s[4] > '9' || s[5] < '0' || s[5] > '9')
205   { 205   {
MISUBC 206   ec = make_error_code(error::invalid_hash); 206   ec = make_error_code(error::invalid_hash);
MISUBC 207   return 0; 207   return 0;
208   } 208   }
209   209  
HITCBC 210   2 unsigned rounds = static_cast<unsigned>((s[4] - '0') * 10 + (s[5] - '0')); 210   2 unsigned rounds = static_cast<unsigned>((s[4] - '0') * 10 + (s[5] - '0'));
HITCBC 211   2 if (rounds < 4 || rounds > 31) 211   2 if (rounds < 4 || rounds > 31)
212   { 212   {
MISUBC 213   ec = make_error_code(error::invalid_hash); 213   ec = make_error_code(error::invalid_hash);
MISUBC 214   return 0; 214   return 0;
215   } 215   }
216   216  
HITCBC 217   2 return rounds; 217   2 return rounds;
218   } 218   }
219   219  
220   } // bcrypt 220   } // bcrypt
221   } // http 221   } // http
222   } // boost 222   } // boost