8
8
#include < boost/mysql/client_errc.hpp>
9
9
10
10
#include < boost/mysql/impl/internal/sansio/caching_sha2_password.hpp>
11
+ #include < boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp>
12
+
13
+ #include < boost/core/span.hpp>
14
+ #include < boost/test/unit_test.hpp>
15
+
16
+ #include < cstddef>
17
+ #include < openssl/bio.h>
18
+ #include < openssl/evp.h>
19
+ #include < openssl/pem.h>
20
+ #include < vector>
11
21
12
22
#include " test_common/assert_buffer_equals.hpp"
13
23
14
24
using namespace boost ::mysql;
15
25
using namespace boost ::mysql::test;
26
+ using detail::csha2p_encrypt_password;
16
27
using detail::csha2p_hash_password;
17
28
29
+ using buffer_type = boost::container::small_vector<std::uint8_t , 512 >;
30
+
18
31
namespace {
19
32
20
33
BOOST_AUTO_TEST_SUITE (test_handshake_csha2p_hash_password)
@@ -43,11 +56,147 @@ BOOST_AUTO_TEST_CASE(empty_password)
43
56
44
57
BOOST_AUTO_TEST_SUITE_END ()
45
58
59
+ // Encrypting with RSA and OAEP padding involves random numbers for padding.
60
+ // There isn't a reliable way to seed OpenSSL's random number generators so that
61
+ // the output is deterministic. So we do the following:
62
+ // 1. We know the server's public and private keys and the password (the constants below).
63
+ // 2. We capture a scramble and a corresponding ciphertext using Wireshark.
64
+ // 3. We decrypt the ciphertext with OpenSSL to obtain the expected plaintext (the salted password).
65
+ // 4. Tests run csha2p_encrypt_password, decrypt its output, and verify that it matches the expected
66
+ // plaintext.
46
67
BOOST_AUTO_TEST_SUITE (test_handshake_csha2p_encrypt_password)
47
68
69
+ constexpr unsigned char public_key_2048[] = R"%( -----BEGIN PUBLIC KEY-----
70
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA36OYSpdiy1lFDrdO1Vux
71
+ GwjPTo35R2+mXqW2SZV7kH5C6BSCeoTk6STVRBJbgOCabtp5bpUZ+x2bYWOZp4fs
72
+ JakC75CN2YTJoYg5z5U6XUBEWn6WNBpvEoSJaUtrzfU69J07uWqB6v0MdJf3JTgd
73
+ ILfGKvk2T+maxqUiYObs0BJd5eKJZDlUaf2r4a9KC8zGUZzHdgtZEXlkHVNLEbbD
74
+ Ju4KjtCtJCG1NEBAh3oSnNp/Q1FKFywqU7YnEBWI0B9C5UcKNFbg7M35daimXfGp
75
+ V7WJKhO9w7iBJYL1SW+PwyUCh3DNsuSm3nLmuwKhTvGQHZJS/5OVdSHgZhjDnk2V
76
+ WwIDAQAB
77
+ -----END PUBLIC KEY-----
78
+ )%" ;
79
+
80
+ constexpr unsigned char private_key_2048[] = R"%( -----BEGIN PRIVATE KEY-----
81
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDfo5hKl2LLWUUO
82
+ t07VW7EbCM9OjflHb6ZepbZJlXuQfkLoFIJ6hOTpJNVEEluA4Jpu2nlulRn7HZth
83
+ Y5mnh+wlqQLvkI3ZhMmhiDnPlTpdQERafpY0Gm8ShIlpS2vN9Tr0nTu5aoHq/Qx0
84
+ l/clOB0gt8Yq+TZP6ZrGpSJg5uzQEl3l4olkOVRp/avhr0oLzMZRnMd2C1kReWQd
85
+ U0sRtsMm7gqO0K0kIbU0QECHehKc2n9DUUoXLCpTticQFYjQH0LlRwo0VuDszfl1
86
+ qKZd8alXtYkqE73DuIElgvVJb4/DJQKHcM2y5Kbecua7AqFO8ZAdklL/k5V1IeBm
87
+ GMOeTZVbAgMBAAECggEANI0UtDJunKoVeCfK9ofdTiT70dG6yfaKeaMm+pONvZ5t
88
+ ymtHXdLsl3x4QM6vgdFFeNcNwdZ3jHKgmHn3GU7vRso4TmMBciOp3bNNImJGnLMF
89
+ XN5yHTw47XkHcR6v7m25tNFdv2wvqzBbROqQwMY20gFdJ6v3/z89h4A2W97nttyp
90
+ ixcNdSTHOfu6iUceEGi2PjHrvw4STPQeihXFNTnYG7hvlWzAerQ5STx5K2n4JoXz
91
+ xI5MuHS6PGj5EBPUoq0+EQhmhORWBdNCMcijpHqobVDjifPRY2JbI3zZHtVjYpGH
92
+ otmc72hIDjNW7RX1ePKW/gsq6p2by3U8+4dOdya4AQKBgQDz4/6uo7atJSTgVo3d
93
+ Zr/7UJ4Qi2mlc992jLAqTQle6JchhNERoPvb19Qy8BaOz6LcxmuMXnRij05mxdyb
94
+ LmoH8TPe6RFLCOJrRapkUjUtRD8dIg6UNFLk98LD5t3o5PoHwNpBFfokRlchwoHL
95
+ uBvbEHQkrPX2xPbgla5e7zJLgQKBgQDqvjCUMvmap9+AlqxGFhv51V9dIlKvT0xW
96
+ p5KEMVfs3JXCHAM7o0ClZ8NitHXw18E8+iMG4pWw3+FX4K6tKGR+rCeGCUNGuiQT
97
+ FzXjrEej+Pnuk8zacjXkbS+PNnEqhpSq6STZVFn2UW+ZWAoq2iAMR7qw0eAjylln
98
+ h//Wad3+2wKBgAjwWEtKUM2zyNA4G+b7dxnc8I4mre6UeqI7sdE7FZbW64Mc/RSq
99
+ U9DQ7kQXrJv7XDq/Qv3YEGf0XKlDozxEzToRSxdmb23Sm4nW+dHHeY95Kt8EeohQ
100
+ CqG9uvO3KHb6vXc/SECOb6aYtWTVXjB7RPoYdklJ1ZH/0hSVJ9ju52cBAoGBAK63
101
+ A90p25F6ZOWGP46iogve/e2JwFTvBnhwnKJ7P1/yBhzFULqwlUsG4euzOR0a2J6T
102
+ 5kIXnyZYW5ZWimwi5jlJ1Nj0R/h6TqNO4TMlZOTsSMmDhDMKUoZDpeRHtw7ZwAk9
103
+ IcoH+DVXA2L0ngyq8LNzJ8a3TsYUs1pVZNunTC2FAoGARe4x29tdri8akxxwF3BV
104
+ bjJ9qRUIfIDK8rGWRdw94vVCB7XVmSWCEchmLqA1DqGYvAhYMYjkXTg9akfBTUQS
105
+ s+8JasUuQam8Y88JAfC0QqGbLgUsh0TpRUOXj+YQuoNiMVu14NNgYgFkx71WtvAq
106
+ kUmkxr/moPcZ+O1ahVjv/Us=
107
+ -----END PRIVATE KEY-----
108
+ )%" ;
109
+
110
+ // Decrypts the output of
111
+ std::vector<std::uint8_t > decrypt (
112
+ boost::span<const std::uint8_t > private_key,
113
+ boost::span<const std::uint8_t > ciphertext
114
+ )
115
+ {
116
+ // RAII helpers
117
+ struct bio_deleter
118
+ {
119
+ void operator ()(BIO* bio) const noexcept { BIO_free (bio); }
120
+ };
121
+ using unique_bio = std::unique_ptr<BIO, bio_deleter>;
122
+
123
+ struct evp_pkey_deleter
124
+ {
125
+ void operator ()(EVP_PKEY* pkey) const noexcept { EVP_PKEY_free (pkey); }
126
+ };
127
+ using unique_evp_pkey = std::unique_ptr<EVP_PKEY, evp_pkey_deleter>;
128
+
129
+ struct evp_pkey_ctx_deleter
130
+ {
131
+ void operator ()(EVP_PKEY_CTX* ctx) const noexcept { EVP_PKEY_CTX_free (ctx); }
132
+ };
133
+ using unique_evp_pkey_ctx = std::unique_ptr<EVP_PKEY_CTX, evp_pkey_ctx_deleter>;
134
+
135
+ // Create a BIO with the key
136
+ unique_bio bio (BIO_new_mem_buf (private_key.data (), private_key.size ()));
137
+ BOOST_TEST_REQUIRE (bio != nullptr , " Creating a BIO failed: " << ERR_get_error ());
138
+
139
+ // Load the key
140
+ unique_evp_pkey pkey (PEM_read_bio_PrivateKey (bio.get (), nullptr , nullptr , nullptr ));
141
+ BOOST_TEST_REQUIRE (bio != nullptr , " Loading the key failed: " << ERR_get_error ());
142
+
143
+ // Create a decryption context
144
+ unique_evp_pkey_ctx ctx (EVP_PKEY_CTX_new (pkey.get (), nullptr ));
145
+ BOOST_TEST_REQUIRE (ctx != nullptr , " Creating a context failed: " << ERR_get_error ());
146
+
147
+ // Initialize it
148
+ BOOST_TEST_REQUIRE (
149
+ EVP_PKEY_decrypt_init (ctx.get ()) > 0 ,
150
+ " Initializing decryption failed: " << ERR_get_error ()
151
+ );
152
+
153
+ // Set the padding scheme
154
+ BOOST_TEST_REQUIRE (
155
+ EVP_PKEY_CTX_set_rsa_padding (ctx.get (), RSA_PKCS1_OAEP_PADDING) > 0 ,
156
+ " Setting the padding scheme failed: " << ERR_get_error ()
157
+ );
158
+
159
+ // Determine the size of the decrypted buffer
160
+ std::size_t out_size = 0 ;
161
+ BOOST_TEST_REQUIRE (
162
+ EVP_PKEY_decrypt (ctx.get (), nullptr , &out_size, ciphertext.data (), ciphertext.size ()) > 0 ,
163
+ " Determining decryption size failed: " << ERR_get_error ()
164
+ );
165
+ std::vector<std::uint8_t > res (out_size, 0 );
166
+
167
+ // Actually decrypt
168
+ BOOST_TEST_REQUIRE (
169
+ EVP_PKEY_decrypt (ctx.get (), res.data (), &out_size, ciphertext.data (), ciphertext.size ()) > 0 ,
170
+ " Decrypting failed: " << ERR_get_error ()
171
+ );
172
+ res.resize (out_size);
173
+
174
+ // Done
175
+ return res;
176
+ }
177
+
178
+ //
179
+ BOOST_AUTO_TEST_CASE (password_lt20)
180
+ {
181
+ // Setup
182
+ constexpr std::uint8_t scramble[] = {
183
+ 0x0f , 0x64 , 0x4f , 0x2f , 0x2b , 0x3b , 0x27 , 0x6b , 0x45 , 0x5c ,
184
+ 0x53 , 0x01 , 0x13 , 0x7e , 0x4f , 0x10 , 0x26 , 0x23 , 0x5d , 0x27 ,
185
+ };
186
+ constexpr std::uint8_t expected_decrypted[] =
187
+ {0x6c , 0x17 , 0x27 , 0x4e , 0x19 , 0x4b , 0x78 , 0x1b , 0x24 , 0x2f , 0x20 , 0x76 , 0x7c , 0x0c , 0x2b , 0x10 };
188
+ buffer_type buff;
189
+
190
+ // Call the function
191
+ unsigned long err = csha2p_encrypt_password (" csha2p_password" , scramble, public_key_2048, buff);
192
+
193
+ // Verify
194
+ BOOST_TEST_REQUIRE (err == 0u );
195
+ BOOST_MYSQL_ASSERT_BUFFER_EQUALS (decrypt (private_key_2048, buff), expected_decrypted);
196
+ }
197
+
48
198
/* *
49
199
encrypt password success
50
- success
51
200
success password is 20 bytes
52
201
success password is > 20 bytes
53
202
success password is max length
@@ -72,6 +221,7 @@ encrypting
72
221
the returned size is > buffer (mock)
73
222
encryption fails (probably merge with the one below)
74
223
password is too big for encryption (with 2 sizes)
224
+ buffer is reset?
75
225
*/
76
226
77
227
BOOST_AUTO_TEST_SUITE_END ()
0 commit comments