86.49% Lines (64/74) 100.00% Functions (7/7)
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 "crypt.hpp" 10   #include "crypt.hpp"
11   #include "base64.hpp" 11   #include "base64.hpp"
12   #include "blowfish.hpp" 12   #include "blowfish.hpp"
13   #include "random.hpp" 13   #include "random.hpp"
14   #include <cstring> 14   #include <cstring>
15   #include <algorithm> 15   #include <algorithm>
16   16  
17   namespace boost { 17   namespace boost {
18   namespace http { 18   namespace http {
19   namespace bcrypt { 19   namespace bcrypt {
20   namespace detail { 20   namespace detail {
21   21  
22   namespace { 22   namespace {
23   23  
24   // "OrpheanBeholderScryDoubt" - magic string for bcrypt 24   // "OrpheanBeholderScryDoubt" - magic string for bcrypt
25   constexpr std::uint8_t magic_text[24] = { 25   constexpr std::uint8_t magic_text[24] = {
26   'O', 'r', 'p', 'h', 'e', 'a', 'n', 'B', 26   'O', 'r', 'p', 'h', 'e', 'a', 'n', 'B',
27   'e', 'h', 'o', 'l', 'd', 'e', 'r', 'S', 27   'e', 'h', 'o', 'l', 'd', 'e', 'r', 'S',
28   'c', 'r', 'y', 'D', 'o', 'u', 'b', 't' 28   'c', 'r', 'y', 'D', 'o', 'u', 'b', 't'
29   }; 29   };
30   30  
HITCBC 31   25 char const* version_prefix(version ver) 31   25 char const* version_prefix(version ver)
32   { 32   {
HITCBC 33   25 switch (ver) 33   25 switch (ver)
34   { 34   {
HITCBC 35   1 case version::v2a: return "$2a$"; 35   1 case version::v2a: return "$2a$";
HITCBC 36   24 case version::v2b: return "$2b$"; 36   24 case version::v2b: return "$2b$";
MISUBC 37   default: return "$2b$"; 37   default: return "$2b$";
38   } 38   }
39   } 39   }
40   40  
41   } // namespace 41   } // namespace
42   42  
HITCBC 43   20 void generate_salt_bytes(std::uint8_t* salt) 43   20 void generate_salt_bytes(std::uint8_t* salt)
44   { 44   {
HITCBC 45   20 fill_random(salt, BCRYPT_SALT_LEN); 45   20 fill_random(salt, BCRYPT_SALT_LEN);
HITCBC 46   20 } 46   20 }
47   47  
HITCBC 48   25 std::size_t format_salt( 48   25 std::size_t format_salt(
49   char* output, 49   char* output,
50   std::uint8_t const* salt_bytes, 50   std::uint8_t const* salt_bytes,
51   unsigned rounds, 51   unsigned rounds,
52   version ver) 52   version ver)
53   { 53   {
HITCBC 54   25 char* p = output; 54   25 char* p = output;
55   55  
56   // Version prefix 56   // Version prefix
HITCBC 57   25 char const* prefix = version_prefix(ver); 57   25 char const* prefix = version_prefix(ver);
HITCBC 58   25 std::size_t prefix_len = 4; 58   25 std::size_t prefix_len = 4;
HITCBC 59   25 std::memcpy(p, prefix, prefix_len); 59   25 std::memcpy(p, prefix, prefix_len);
HITCBC 60   25 p += prefix_len; 60   25 p += prefix_len;
61   61  
62   // Rounds (2 digits, zero-padded) 62   // Rounds (2 digits, zero-padded)
HITCBC 63   25 *p++ = static_cast<char>('0' + (rounds / 10)); 63   25 *p++ = static_cast<char>('0' + (rounds / 10));
HITCBC 64   25 *p++ = static_cast<char>('0' + (rounds % 10)); 64   25 *p++ = static_cast<char>('0' + (rounds % 10));
HITCBC 65   25 *p++ = '$'; 65   25 *p++ = '$';
66   66  
67   // Salt (22 base64 characters) 67   // Salt (22 base64 characters)
HITCBC 68   25 std::size_t encoded = base64_encode(p, salt_bytes, BCRYPT_SALT_LEN); 68   25 std::size_t encoded = base64_encode(p, salt_bytes, BCRYPT_SALT_LEN);
HITCBC 69   25 p += encoded; 69   25 p += encoded;
70   70  
HITCBC 71   25 return static_cast<std::size_t>(p - output); 71   25 return static_cast<std::size_t>(p - output);
72   } 72   }
73   73  
HITCBC 74   21 bool parse_salt( 74   21 bool parse_salt(
75   core::string_view salt_str, 75   core::string_view salt_str,
76   version& ver, 76   version& ver,
77   unsigned& rounds, 77   unsigned& rounds,
78   std::uint8_t* salt_bytes) 78   std::uint8_t* salt_bytes)
79   { 79   {
80   // Minimum: "$2a$XX$" + 22 chars = 29 80   // Minimum: "$2a$XX$" + 22 chars = 29
HITCBC 81   21 if (salt_str.size() < 29) 81   21 if (salt_str.size() < 29)
HITCBC 82   6 return false; 82   6 return false;
83   83  
HITCBC 84   15 char const* s = salt_str.data(); 84   15 char const* s = salt_str.data();
85   85  
86   // Check prefix 86   // Check prefix
HITCBC 87   15 if (s[0] != '$' || s[1] != '2') 87   15 if (s[0] != '$' || s[1] != '2')
MISUBC 88   return false; 88   return false;
89   89  
90   // Parse version 90   // Parse version
HITCBC 91   15 if (s[2] == 'a' && s[3] == '$') 91   15 if (s[2] == 'a' && s[3] == '$')
HITCBC 92   3 ver = version::v2a; 92   3 ver = version::v2a;
HITCBC 93   12 else if (s[2] == 'b' && s[3] == '$') 93   12 else if (s[2] == 'b' && s[3] == '$')
HITCBC 94   12 ver = version::v2b; 94   12 ver = version::v2b;
MISUBC 95   else if (s[2] == 'y' && s[3] == '$') 95   else if (s[2] == 'y' && s[3] == '$')
MISUBC 96   ver = version::v2b; // treat $2y$ as $2b$ 96   ver = version::v2b; // treat $2y$ as $2b$
97   else 97   else
MISUBC 98   return false; 98   return false;
99   99  
100   // Parse rounds 100   // Parse rounds
HITCBC 101   15 if (s[4] < '0' || s[4] > '9') 101   15 if (s[4] < '0' || s[4] > '9')
MISUBC 102   return false; 102   return false;
HITCBC 103   15 if (s[5] < '0' || s[5] > '9') 103   15 if (s[5] < '0' || s[5] > '9')
MISUBC 104   return false; 104   return false;
105   105  
HITCBC 106   15 rounds = static_cast<unsigned>((s[4] - '0') * 10 + (s[5] - '0')); 106   15 rounds = static_cast<unsigned>((s[4] - '0') * 10 + (s[5] - '0'));
HITCBC 107   15 if (rounds < 4 || rounds > 31) 107   15 if (rounds < 4 || rounds > 31)
MISUBC 108   return false; 108   return false;
109   109  
HITCBC 110   15 if (s[6] != '$') 110   15 if (s[6] != '$')
MISUBC 111   return false; 111   return false;
112   112  
113   // Decode salt (22 base64 chars -> 16 bytes) 113   // Decode salt (22 base64 chars -> 16 bytes)
HITCBC 114   15 int decoded = base64_decode(salt_bytes, s + 7, 22); 114   15 int decoded = base64_decode(salt_bytes, s + 7, 22);
HITCBC 115   15 if (decoded != 16) 115   15 if (decoded != 16)
MISUBC 116   return false; 116   return false;
117   117  
HITCBC 118   15 return true; 118   15 return true;
119   } 119   }
120   120  
HITCBC 121   28 void bcrypt_hash( 121   28 void bcrypt_hash(
122   char const* password, 122   char const* password,
123   std::size_t password_len, 123   std::size_t password_len,
124   std::uint8_t const* salt, 124   std::uint8_t const* salt,
125   unsigned rounds, 125   unsigned rounds,
126   std::uint8_t* hash) 126   std::uint8_t* hash)
127   { 127   {
128   blowfish_ctx ctx; 128   blowfish_ctx ctx;
129   129  
130   // Truncate password to 72 bytes (bcrypt limit) 130   // Truncate password to 72 bytes (bcrypt limit)
131   // Include null terminator in hash 131   // Include null terminator in hash
HITCBC 132   28 std::size_t key_len = std::min(password_len, std::size_t(72)); 132   28 std::size_t key_len = std::min(password_len, std::size_t(72));
133   133  
134   // Create key with null terminator 134   // Create key with null terminator
135   std::uint8_t key[73]; 135   std::uint8_t key[73];
HITCBC 136   28 std::memcpy(key, password, key_len); 136   28 std::memcpy(key, password, key_len);
HITCBC 137   28 key[key_len] = 0; 137   28 key[key_len] = 0;
HITCBC 138   28 key_len++; 138   28 key_len++;
139   139  
140   // Initialize with default P and S boxes 140   // Initialize with default P and S boxes
HITCBC 141   28 blowfish_init(ctx); 141   28 blowfish_init(ctx);
142   142  
143   // Expensive key setup (eksblowfish) 143   // Expensive key setup (eksblowfish)
HITCBC 144   28 blowfish_expand_key_salt(ctx, key, key_len, salt, BCRYPT_SALT_LEN); 144   28 blowfish_expand_key_salt(ctx, key, key_len, salt, BCRYPT_SALT_LEN);
145   145  
146   // 2^rounds iterations 146   // 2^rounds iterations
HITCBC 147   28 std::uint64_t iterations = 1ULL << rounds; 147   28 std::uint64_t iterations = 1ULL << rounds;
HITCBC 148   556 for (std::uint64_t i = 0; i < iterations; ++i) 148   556 for (std::uint64_t i = 0; i < iterations; ++i)
149   { 149   {
HITCBC 150   528 blowfish_expand_key(ctx, key, key_len); 150   528 blowfish_expand_key(ctx, key, key_len);
HITCBC 151   528 blowfish_expand_key(ctx, salt, BCRYPT_SALT_LEN); 151   528 blowfish_expand_key(ctx, salt, BCRYPT_SALT_LEN);
152   } 152   }
153   153  
154   // Encrypt magic text 64 times 154   // Encrypt magic text 64 times
155   std::uint8_t ctext[24]; 155   std::uint8_t ctext[24];
HITCBC 156   28 std::memcpy(ctext, magic_text, 24); 156   28 std::memcpy(ctext, magic_text, 24);
157   157  
HITCBC 158   1820 for (int i = 0; i < 64; ++i) 158   1820 for (int i = 0; i < 64; ++i)
159   { 159   {
HITCBC 160   1792 blowfish_encrypt_ecb(ctx, ctext, 24); 160   1792 blowfish_encrypt_ecb(ctx, ctext, 24);
161   } 161   }
162   162  
163   // Copy result (only 23 bytes are used in the final encoding) 163   // Copy result (only 23 bytes are used in the final encoding)
HITCBC 164   28 std::memcpy(hash, ctext, 24); 164   28 std::memcpy(hash, ctext, 24);
165   165  
166   // Clear sensitive data 166   // Clear sensitive data
HITCBC 167   28 std::memset(&ctx, 0, sizeof(ctx)); 167   28 std::memset(&ctx, 0, sizeof(ctx));
HITCBC 168   28 std::memset(key, 0, sizeof(key)); 168   28 std::memset(key, 0, sizeof(key));
HITCBC 169   28 } 169   28 }
170   170  
HITCBC 171   18 std::size_t format_hash( 171   18 std::size_t format_hash(
172   char* output, 172   char* output,
173   std::uint8_t const* salt_bytes, 173   std::uint8_t const* salt_bytes,
174   std::uint8_t const* hash_bytes, 174   std::uint8_t const* hash_bytes,
175   unsigned rounds, 175   unsigned rounds,
176   version ver) 176   version ver)
177   { 177   {
HITCBC 178   18 char* p = output; 178   18 char* p = output;
179   179  
180   // Format salt portion (29 chars) 180   // Format salt portion (29 chars)
HITCBC 181   18 p += format_salt(p, salt_bytes, rounds, ver); 181   18 p += format_salt(p, salt_bytes, rounds, ver);
182   182  
183   // Encode hash (23 bytes -> 31 base64 chars) 183   // Encode hash (23 bytes -> 31 base64 chars)
184   // Note: bcrypt only uses 23 of the 24 hash bytes 184   // Note: bcrypt only uses 23 of the 24 hash bytes
HITCBC 185   18 p += base64_encode(p, hash_bytes, 23); 185   18 p += base64_encode(p, hash_bytes, 23);
186   186  
HITCBC 187   18 return static_cast<std::size_t>(p - output); 187   18 return static_cast<std::size_t>(p - output);
188   } 188   }
189   189  
HITCBC 190   10 bool secure_compare( 190   10 bool secure_compare(
191   std::uint8_t const* a, 191   std::uint8_t const* a,
192   std::uint8_t const* b, 192   std::uint8_t const* b,
193   std::size_t len) 193   std::size_t len)
194   { 194   {
HITCBC 195   10 volatile std::uint8_t result = 0; 195   10 volatile std::uint8_t result = 0;
HITCBC 196   240 for (std::size_t i = 0; i < len; ++i) 196   240 for (std::size_t i = 0; i < len; ++i)
197   { 197   {
HITCBC 198   230 result = static_cast<std::uint8_t>(result | (a[i] ^ b[i])); 198   230 result = static_cast<std::uint8_t>(result | (a[i] ^ b[i]));
199   } 199   }
HITCBC 200   10 return result == 0; 200   10 return result == 0;
201   } 201   }
202   202  
203   } // detail 203   } // detail
204   } // bcrypt 204   } // bcrypt
205   } // http 205   } // http
206   } // boost 206   } // boost