]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #include <array> | |
5 | #include <openssl/evp.h> | |
6 | ||
7 | #include "crypto_onwire.h" | |
8 | ||
9 | #include "common/debug.h" | |
92f5a8d4 | 10 | #include "common/ceph_crypto.h" |
11fdf7f2 TL |
11 | #include "include/types.h" |
12 | ||
13 | #define dout_subsys ceph_subsys_ms | |
14 | ||
15 | namespace ceph::crypto::onwire { | |
16 | ||
17 | static constexpr const std::size_t AESGCM_KEY_LEN{16}; | |
18 | static constexpr const std::size_t AESGCM_IV_LEN{12}; | |
19 | static constexpr const std::size_t AESGCM_TAG_LEN{16}; | |
20 | static constexpr const std::size_t AESGCM_BLOCK_LEN{16}; | |
21 | ||
22 | struct nonce_t { | |
f6b5b4d7 TL |
23 | ceph_le32 fixed; |
24 | ceph_le64 counter; | |
801d1391 TL |
25 | |
26 | bool operator==(const nonce_t& rhs) const { | |
27 | return !memcmp(this, &rhs, sizeof(*this)); | |
28 | } | |
11fdf7f2 TL |
29 | } __attribute__((packed)); |
30 | static_assert(sizeof(nonce_t) == AESGCM_IV_LEN); | |
31 | ||
32 | using key_t = std::array<std::uint8_t, AESGCM_KEY_LEN>; | |
33 | ||
34 | // http://www.mindspring.com/~dmcgrew/gcm-nist-6.pdf | |
35 | // https://www.openssl.org/docs/man1.0.2/crypto/EVP_aes_128_gcm.html#GCM-mode | |
36 | // https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption | |
37 | // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf | |
38 | class AES128GCM_OnWireTxHandler : public ceph::crypto::onwire::TxHandler { | |
39 | CephContext* const cct; | |
40 | std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ectx; | |
41 | ceph::bufferlist buffer; | |
801d1391 TL |
42 | nonce_t nonce, initial_nonce; |
43 | bool used_initial_nonce; | |
f6b5b4d7 | 44 | bool new_nonce_format; // 64-bit counter? |
11fdf7f2 TL |
45 | static_assert(sizeof(nonce) == AESGCM_IV_LEN); |
46 | ||
47 | public: | |
48 | AES128GCM_OnWireTxHandler(CephContext* const cct, | |
49 | const key_t& key, | |
f6b5b4d7 TL |
50 | const nonce_t& nonce, |
51 | bool new_nonce_format) | |
11fdf7f2 TL |
52 | : cct(cct), |
53 | ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free), | |
f6b5b4d7 TL |
54 | nonce(nonce), initial_nonce(nonce), used_initial_nonce(false), |
55 | new_nonce_format(new_nonce_format) { | |
11fdf7f2 TL |
56 | ceph_assert_always(ectx); |
57 | ceph_assert_always(key.size() * CHAR_BIT == 128); | |
58 | ||
59 | if (1 != EVP_EncryptInit_ex(ectx.get(), EVP_aes_128_gcm(), | |
60 | nullptr, nullptr, nullptr)) { | |
61 | throw std::runtime_error("EVP_EncryptInit_ex failed"); | |
62 | } | |
63 | ||
64 | if(1 != EVP_EncryptInit_ex(ectx.get(), nullptr, nullptr, | |
65 | key.data(), nullptr)) { | |
66 | throw std::runtime_error("EVP_EncryptInit_ex failed"); | |
67 | } | |
68 | } | |
69 | ||
70 | ~AES128GCM_OnWireTxHandler() override { | |
92f5a8d4 | 71 | ::ceph::crypto::zeroize_for_security(&nonce, sizeof(nonce)); |
801d1391 | 72 | ::ceph::crypto::zeroize_for_security(&initial_nonce, sizeof(initial_nonce)); |
11fdf7f2 TL |
73 | } |
74 | ||
f6b5b4d7 | 75 | void reset_tx_handler(const uint32_t* first, const uint32_t* last) override; |
11fdf7f2 TL |
76 | |
77 | void authenticated_encrypt_update(const ceph::bufferlist& plaintext) override; | |
78 | ceph::bufferlist authenticated_encrypt_final() override; | |
79 | }; | |
80 | ||
f6b5b4d7 TL |
81 | void AES128GCM_OnWireTxHandler::reset_tx_handler(const uint32_t* first, |
82 | const uint32_t* last) | |
11fdf7f2 | 83 | { |
801d1391 TL |
84 | if (nonce == initial_nonce) { |
85 | if (used_initial_nonce) { | |
86 | throw ceph::crypto::onwire::TxHandlerError("out of nonces"); | |
87 | } | |
88 | used_initial_nonce = true; | |
89 | } | |
90 | ||
11fdf7f2 TL |
91 | if(1 != EVP_EncryptInit_ex(ectx.get(), nullptr, nullptr, nullptr, |
92 | reinterpret_cast<const unsigned char*>(&nonce))) { | |
93 | throw std::runtime_error("EVP_EncryptInit_ex failed"); | |
94 | } | |
95 | ||
f6b5b4d7 TL |
96 | ceph_assert(buffer.get_append_buffer_unused_tail_length() == 0); |
97 | buffer.reserve(std::accumulate(first, last, AESGCM_TAG_LEN)); | |
11fdf7f2 | 98 | |
f6b5b4d7 TL |
99 | if (!new_nonce_format) { |
100 | // msgr2.0: 32-bit counter followed by 64-bit fixed field, | |
101 | // susceptible to overflow! | |
102 | nonce.fixed = nonce.fixed + 1; | |
103 | } else { | |
104 | nonce.counter = nonce.counter + 1; | |
105 | } | |
11fdf7f2 TL |
106 | } |
107 | ||
108 | void AES128GCM_OnWireTxHandler::authenticated_encrypt_update( | |
109 | const ceph::bufferlist& plaintext) | |
110 | { | |
f6b5b4d7 TL |
111 | ceph_assert(buffer.get_append_buffer_unused_tail_length() >= |
112 | plaintext.length()); | |
11fdf7f2 TL |
113 | auto filler = buffer.append_hole(plaintext.length()); |
114 | ||
115 | for (const auto& plainbuf : plaintext.buffers()) { | |
116 | int update_len = 0; | |
117 | ||
118 | if(1 != EVP_EncryptUpdate(ectx.get(), | |
119 | reinterpret_cast<unsigned char*>(filler.c_str()), | |
120 | &update_len, | |
121 | reinterpret_cast<const unsigned char*>(plainbuf.c_str()), | |
122 | plainbuf.length())) { | |
123 | throw std::runtime_error("EVP_EncryptUpdate failed"); | |
124 | } | |
125 | ceph_assert_always(update_len >= 0); | |
126 | ceph_assert(static_cast<unsigned>(update_len) == plainbuf.length()); | |
127 | filler.advance(update_len); | |
128 | } | |
129 | ||
130 | ldout(cct, 15) << __func__ | |
131 | << " plaintext.length()=" << plaintext.length() | |
132 | << " buffer.length()=" << buffer.length() | |
133 | << dendl; | |
134 | } | |
135 | ||
136 | ceph::bufferlist AES128GCM_OnWireTxHandler::authenticated_encrypt_final() | |
137 | { | |
138 | int final_len = 0; | |
f6b5b4d7 TL |
139 | ceph_assert(buffer.get_append_buffer_unused_tail_length() == |
140 | AESGCM_BLOCK_LEN); | |
11fdf7f2 TL |
141 | auto filler = buffer.append_hole(AESGCM_BLOCK_LEN); |
142 | if(1 != EVP_EncryptFinal_ex(ectx.get(), | |
143 | reinterpret_cast<unsigned char*>(filler.c_str()), | |
144 | &final_len)) { | |
145 | throw std::runtime_error("EVP_EncryptFinal_ex failed"); | |
146 | } | |
147 | ceph_assert_always(final_len == 0); | |
148 | ||
149 | static_assert(AESGCM_BLOCK_LEN == AESGCM_TAG_LEN); | |
150 | if(1 != EVP_CIPHER_CTX_ctrl(ectx.get(), | |
151 | EVP_CTRL_GCM_GET_TAG, AESGCM_TAG_LEN, | |
152 | filler.c_str())) { | |
153 | throw std::runtime_error("EVP_CIPHER_CTX_ctrl failed"); | |
154 | } | |
155 | ||
156 | ldout(cct, 15) << __func__ | |
157 | << " buffer.length()=" << buffer.length() | |
158 | << " final_len=" << final_len | |
159 | << dendl; | |
160 | return std::move(buffer); | |
161 | } | |
162 | ||
163 | // RX PART | |
164 | class AES128GCM_OnWireRxHandler : public ceph::crypto::onwire::RxHandler { | |
165 | CephContext* const cct; | |
166 | std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ectx; | |
167 | nonce_t nonce; | |
f6b5b4d7 | 168 | bool new_nonce_format; // 64-bit counter? |
11fdf7f2 TL |
169 | static_assert(sizeof(nonce) == AESGCM_IV_LEN); |
170 | ||
171 | public: | |
172 | AES128GCM_OnWireRxHandler(CephContext* const cct, | |
173 | const key_t& key, | |
f6b5b4d7 TL |
174 | const nonce_t& nonce, |
175 | bool new_nonce_format) | |
11fdf7f2 TL |
176 | : cct(cct), |
177 | ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free), | |
f6b5b4d7 | 178 | nonce(nonce), new_nonce_format(new_nonce_format) { |
11fdf7f2 TL |
179 | ceph_assert_always(ectx); |
180 | ceph_assert_always(key.size() * CHAR_BIT == 128); | |
181 | ||
182 | if (1 != EVP_DecryptInit_ex(ectx.get(), EVP_aes_128_gcm(), | |
183 | nullptr, nullptr, nullptr)) { | |
184 | throw std::runtime_error("EVP_DecryptInit_ex failed"); | |
185 | } | |
186 | ||
187 | if(1 != EVP_DecryptInit_ex(ectx.get(), nullptr, nullptr, | |
188 | key.data(), nullptr)) { | |
189 | throw std::runtime_error("EVP_DecryptInit_ex failed"); | |
190 | } | |
191 | } | |
192 | ||
193 | ~AES128GCM_OnWireRxHandler() override { | |
92f5a8d4 | 194 | ::ceph::crypto::zeroize_for_security(&nonce, sizeof(nonce)); |
11fdf7f2 TL |
195 | } |
196 | ||
197 | std::uint32_t get_extra_size_at_final() override { | |
198 | return AESGCM_TAG_LEN; | |
199 | } | |
200 | void reset_rx_handler() override; | |
f6b5b4d7 TL |
201 | void authenticated_decrypt_update(ceph::bufferlist& bl) override; |
202 | void authenticated_decrypt_update_final(ceph::bufferlist& bl) override; | |
11fdf7f2 TL |
203 | }; |
204 | ||
205 | void AES128GCM_OnWireRxHandler::reset_rx_handler() | |
206 | { | |
207 | if(1 != EVP_DecryptInit_ex(ectx.get(), nullptr, nullptr, nullptr, | |
208 | reinterpret_cast<const unsigned char*>(&nonce))) { | |
209 | throw std::runtime_error("EVP_DecryptInit_ex failed"); | |
210 | } | |
f6b5b4d7 TL |
211 | |
212 | if (!new_nonce_format) { | |
213 | // msgr2.0: 32-bit counter followed by 64-bit fixed field, | |
214 | // susceptible to overflow! | |
215 | nonce.fixed = nonce.fixed + 1; | |
216 | } else { | |
217 | nonce.counter = nonce.counter + 1; | |
218 | } | |
11fdf7f2 TL |
219 | } |
220 | ||
f6b5b4d7 TL |
221 | void AES128GCM_OnWireRxHandler::authenticated_decrypt_update( |
222 | ceph::bufferlist& bl) | |
11fdf7f2 | 223 | { |
f6b5b4d7 TL |
224 | // discard cached crcs as we will be writing through c_str() |
225 | bl.invalidate_crc(); | |
226 | for (auto& buf : bl.buffers()) { | |
227 | auto p = reinterpret_cast<unsigned char*>(const_cast<char*>(buf.c_str())); | |
11fdf7f2 TL |
228 | int update_len = 0; |
229 | ||
f6b5b4d7 | 230 | if (1 != EVP_DecryptUpdate(ectx.get(), p, &update_len, p, buf.length())) { |
11fdf7f2 TL |
231 | throw std::runtime_error("EVP_DecryptUpdate failed"); |
232 | } | |
233 | ceph_assert_always(update_len >= 0); | |
f6b5b4d7 | 234 | ceph_assert(static_cast<unsigned>(update_len) == buf.length()); |
11fdf7f2 | 235 | } |
11fdf7f2 TL |
236 | } |
237 | ||
f6b5b4d7 TL |
238 | void AES128GCM_OnWireRxHandler::authenticated_decrypt_update_final( |
239 | ceph::bufferlist& bl) | |
11fdf7f2 | 240 | { |
f6b5b4d7 TL |
241 | unsigned orig_len = bl.length(); |
242 | ceph_assert(orig_len >= AESGCM_TAG_LEN); | |
11fdf7f2 TL |
243 | |
244 | // decrypt optional data. Caller is obliged to provide only signature but it | |
245 | // may supply ciphertext as well. Combining the update + final is reflected | |
246 | // combined together. | |
11fdf7f2 | 247 | ceph::bufferlist auth_tag; |
f6b5b4d7 TL |
248 | bl.splice(orig_len - AESGCM_TAG_LEN, AESGCM_TAG_LEN, &auth_tag); |
249 | if (bl.length() > 0) { | |
250 | authenticated_decrypt_update(bl); | |
11fdf7f2 TL |
251 | } |
252 | ||
253 | // we need to ensure the tag is stored in continuous memory. | |
254 | if (1 != EVP_CIPHER_CTX_ctrl(ectx.get(), EVP_CTRL_GCM_SET_TAG, | |
255 | AESGCM_TAG_LEN, auth_tag.c_str())) { | |
256 | throw std::runtime_error("EVP_CIPHER_CTX_ctrl failed"); | |
257 | } | |
258 | ||
259 | // I expect that 0 bytes will be appended. The call is supposed solely to | |
260 | // authenticate the message. | |
261 | { | |
262 | int final_len = 0; | |
263 | if (0 >= EVP_DecryptFinal_ex(ectx.get(), nullptr, &final_len)) { | |
11fdf7f2 | 264 | throw MsgAuthError(); |
11fdf7f2 | 265 | } |
f6b5b4d7 TL |
266 | ceph_assert_always(final_len == 0); |
267 | ceph_assert(bl.length() + AESGCM_TAG_LEN == orig_len); | |
11fdf7f2 | 268 | } |
11fdf7f2 TL |
269 | } |
270 | ||
271 | ceph::crypto::onwire::rxtx_t ceph::crypto::onwire::rxtx_t::create_handler_pair( | |
272 | CephContext* cct, | |
273 | const AuthConnectionMeta& auth_meta, | |
f6b5b4d7 | 274 | bool new_nonce_format, |
11fdf7f2 TL |
275 | bool crossed) |
276 | { | |
277 | if (auth_meta.is_mode_secure()) { | |
278 | ceph_assert_always(auth_meta.connection_secret.length() >= \ | |
279 | sizeof(key_t) + 2 * sizeof(nonce_t)); | |
280 | const char* secbuf = auth_meta.connection_secret.c_str(); | |
281 | ||
282 | key_t key; | |
283 | { | |
284 | ::memcpy(key.data(), secbuf, sizeof(key)); | |
285 | secbuf += sizeof(key); | |
286 | } | |
287 | ||
288 | nonce_t rx_nonce; | |
289 | { | |
290 | ::memcpy(&rx_nonce, secbuf, sizeof(rx_nonce)); | |
291 | secbuf += sizeof(rx_nonce); | |
292 | } | |
293 | ||
294 | nonce_t tx_nonce; | |
295 | { | |
296 | ::memcpy(&tx_nonce, secbuf, sizeof(tx_nonce)); | |
297 | secbuf += sizeof(tx_nonce); | |
298 | } | |
299 | ||
300 | return { | |
301 | std::make_unique<AES128GCM_OnWireRxHandler>( | |
f6b5b4d7 | 302 | cct, key, crossed ? tx_nonce : rx_nonce, new_nonce_format), |
11fdf7f2 | 303 | std::make_unique<AES128GCM_OnWireTxHandler>( |
f6b5b4d7 | 304 | cct, key, crossed ? rx_nonce : tx_nonce, new_nonce_format) |
11fdf7f2 TL |
305 | }; |
306 | } else { | |
307 | return { nullptr, nullptr }; | |
308 | } | |
309 | } | |
310 | ||
311 | } // namespace ceph::crypto::onwire |