]> git.proxmox.com Git - ceph.git/blame - ceph/src/msg/async/crypto_onwire.cc
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / msg / async / crypto_onwire.cc
CommitLineData
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
15namespace ceph::crypto::onwire {
16
17static constexpr const std::size_t AESGCM_KEY_LEN{16};
18static constexpr const std::size_t AESGCM_IV_LEN{12};
19static constexpr const std::size_t AESGCM_TAG_LEN{16};
20static constexpr const std::size_t AESGCM_BLOCK_LEN{16};
21
22struct nonce_t {
23 std::uint32_t random_seq;
24 std::uint64_t random_rest;
25} __attribute__((packed));
26static_assert(sizeof(nonce_t) == AESGCM_IV_LEN);
27
28using key_t = std::array<std::uint8_t, AESGCM_KEY_LEN>;
29
30// http://www.mindspring.com/~dmcgrew/gcm-nist-6.pdf
31// https://www.openssl.org/docs/man1.0.2/crypto/EVP_aes_128_gcm.html#GCM-mode
32// https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption
33// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
34class AES128GCM_OnWireTxHandler : public ceph::crypto::onwire::TxHandler {
35 CephContext* const cct;
36 std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ectx;
37 ceph::bufferlist buffer;
38 nonce_t nonce;
39 static_assert(sizeof(nonce) == AESGCM_IV_LEN);
40
41public:
42 AES128GCM_OnWireTxHandler(CephContext* const cct,
43 const key_t& key,
44 const nonce_t& nonce)
45 : cct(cct),
46 ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free),
47 nonce(nonce) {
48 ceph_assert_always(ectx);
49 ceph_assert_always(key.size() * CHAR_BIT == 128);
50
51 if (1 != EVP_EncryptInit_ex(ectx.get(), EVP_aes_128_gcm(),
52 nullptr, nullptr, nullptr)) {
53 throw std::runtime_error("EVP_EncryptInit_ex failed");
54 }
55
56 if(1 != EVP_EncryptInit_ex(ectx.get(), nullptr, nullptr,
57 key.data(), nullptr)) {
58 throw std::runtime_error("EVP_EncryptInit_ex failed");
59 }
60 }
61
62 ~AES128GCM_OnWireTxHandler() override {
92f5a8d4 63 ::ceph::crypto::zeroize_for_security(&nonce, sizeof(nonce));
11fdf7f2
TL
64 }
65
66 std::uint32_t calculate_segment_size(std::uint32_t size) override
67 {
68 return size;
69 }
70
71 void reset_tx_handler(
72 std::initializer_list<std::uint32_t> update_size_sequence) override;
73
74 void authenticated_encrypt_update(const ceph::bufferlist& plaintext) override;
75 ceph::bufferlist authenticated_encrypt_final() override;
76};
77
78void AES128GCM_OnWireTxHandler::reset_tx_handler(
79 std::initializer_list<std::uint32_t> update_size_sequence)
80{
81 if(1 != EVP_EncryptInit_ex(ectx.get(), nullptr, nullptr, nullptr,
82 reinterpret_cast<const unsigned char*>(&nonce))) {
83 throw std::runtime_error("EVP_EncryptInit_ex failed");
84 }
85
86 buffer.reserve(std::accumulate(std::begin(update_size_sequence),
87 std::end(update_size_sequence), AESGCM_TAG_LEN));
88
89 ++nonce.random_seq;
90}
91
92void AES128GCM_OnWireTxHandler::authenticated_encrypt_update(
93 const ceph::bufferlist& plaintext)
94{
95 auto filler = buffer.append_hole(plaintext.length());
96
97 for (const auto& plainbuf : plaintext.buffers()) {
98 int update_len = 0;
99
100 if(1 != EVP_EncryptUpdate(ectx.get(),
101 reinterpret_cast<unsigned char*>(filler.c_str()),
102 &update_len,
103 reinterpret_cast<const unsigned char*>(plainbuf.c_str()),
104 plainbuf.length())) {
105 throw std::runtime_error("EVP_EncryptUpdate failed");
106 }
107 ceph_assert_always(update_len >= 0);
108 ceph_assert(static_cast<unsigned>(update_len) == plainbuf.length());
109 filler.advance(update_len);
110 }
111
112 ldout(cct, 15) << __func__
113 << " plaintext.length()=" << plaintext.length()
114 << " buffer.length()=" << buffer.length()
115 << dendl;
116}
117
118ceph::bufferlist AES128GCM_OnWireTxHandler::authenticated_encrypt_final()
119{
120 int final_len = 0;
121 auto filler = buffer.append_hole(AESGCM_BLOCK_LEN);
122 if(1 != EVP_EncryptFinal_ex(ectx.get(),
123 reinterpret_cast<unsigned char*>(filler.c_str()),
124 &final_len)) {
125 throw std::runtime_error("EVP_EncryptFinal_ex failed");
126 }
127 ceph_assert_always(final_len == 0);
128
129 static_assert(AESGCM_BLOCK_LEN == AESGCM_TAG_LEN);
130 if(1 != EVP_CIPHER_CTX_ctrl(ectx.get(),
131 EVP_CTRL_GCM_GET_TAG, AESGCM_TAG_LEN,
132 filler.c_str())) {
133 throw std::runtime_error("EVP_CIPHER_CTX_ctrl failed");
134 }
135
136 ldout(cct, 15) << __func__
137 << " buffer.length()=" << buffer.length()
138 << " final_len=" << final_len
139 << dendl;
140 return std::move(buffer);
141}
142
143// RX PART
144class AES128GCM_OnWireRxHandler : public ceph::crypto::onwire::RxHandler {
145 CephContext* const cct;
146 std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ectx;
147 nonce_t nonce;
148 static_assert(sizeof(nonce) == AESGCM_IV_LEN);
149
150public:
151 AES128GCM_OnWireRxHandler(CephContext* const cct,
152 const key_t& key,
153 const nonce_t& nonce)
154 : cct(cct),
155 ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free),
156 nonce(nonce)
157 {
158 ceph_assert_always(ectx);
159 ceph_assert_always(key.size() * CHAR_BIT == 128);
160
161 if (1 != EVP_DecryptInit_ex(ectx.get(), EVP_aes_128_gcm(),
162 nullptr, nullptr, nullptr)) {
163 throw std::runtime_error("EVP_DecryptInit_ex failed");
164 }
165
166 if(1 != EVP_DecryptInit_ex(ectx.get(), nullptr, nullptr,
167 key.data(), nullptr)) {
168 throw std::runtime_error("EVP_DecryptInit_ex failed");
169 }
170 }
171
172 ~AES128GCM_OnWireRxHandler() override {
92f5a8d4 173 ::ceph::crypto::zeroize_for_security(&nonce, sizeof(nonce));
11fdf7f2
TL
174 }
175
176 std::uint32_t get_extra_size_at_final() override {
177 return AESGCM_TAG_LEN;
178 }
179 void reset_rx_handler() override;
180 ceph::bufferlist authenticated_decrypt_update(
181 ceph::bufferlist&& ciphertext,
182 std::uint32_t alignment) override;
183 ceph::bufferlist authenticated_decrypt_update_final(
184 ceph::bufferlist&& ciphertext,
185 std::uint32_t alignment) override;
186};
187
188void AES128GCM_OnWireRxHandler::reset_rx_handler()
189{
190 if(1 != EVP_DecryptInit_ex(ectx.get(), nullptr, nullptr, nullptr,
191 reinterpret_cast<const unsigned char*>(&nonce))) {
192 throw std::runtime_error("EVP_DecryptInit_ex failed");
193 }
194 ++nonce.random_seq;
195}
196
197ceph::bufferlist AES128GCM_OnWireRxHandler::authenticated_decrypt_update(
198 ceph::bufferlist&& ciphertext,
199 std::uint32_t alignment)
200{
201 ceph_assert(ciphertext.length() > 0);
202 //ceph_assert(ciphertext.length() % AESGCM_BLOCK_LEN == 0);
203
204 // NOTE: we might consider in-place transformations in the future. AFAIK
205 // OpenSSL's might sustain that but lack of clear confirmation postpones.
206 auto plainnode = ceph::buffer::ptr_node::create(buffer::create_aligned(
207 ciphertext.length(), alignment));
208 auto* plainbuf = reinterpret_cast<unsigned char*>(plainnode->c_str());
209
210 for (const auto& cipherbuf : ciphertext.buffers()) {
211 // XXX: Why int?
212 int update_len = 0;
213
214 if (1 != EVP_DecryptUpdate(ectx.get(),
215 plainbuf,
216 &update_len,
217 reinterpret_cast<const unsigned char*>(cipherbuf.c_str()),
218 cipherbuf.length())) {
219 throw std::runtime_error("EVP_DecryptUpdate failed");
220 }
221 ceph_assert_always(update_len >= 0);
222 ceph_assert(cipherbuf.length() == static_cast<unsigned>(update_len));
223
224 plainbuf += update_len;
225 }
226
227 ceph::bufferlist outbl;
228 outbl.push_back(std::move(plainnode));
229 return outbl;
230}
231
232
233ceph::bufferlist AES128GCM_OnWireRxHandler::authenticated_decrypt_update_final(
234 ceph::bufferlist&& ciphertext_and_tag,
235 std::uint32_t alignment)
236{
237 const auto cnt_len = ciphertext_and_tag.length();
238 ceph_assert(cnt_len >= AESGCM_TAG_LEN);
239
240 // decrypt optional data. Caller is obliged to provide only signature but it
241 // may supply ciphertext as well. Combining the update + final is reflected
242 // combined together.
243 ceph::bufferlist plainbl;
244 ceph::bufferlist auth_tag;
245 {
246 const auto tag_off = cnt_len - AESGCM_TAG_LEN;
247 ceph::bufferlist ciphertext;
248 ciphertext_and_tag.splice(0, tag_off, &ciphertext);
249
250 // the rest is the signature (a.k.a auth tag)
251 auth_tag = std::move(ciphertext_and_tag);
252
253 if (ciphertext.length()) {
254 plainbl = authenticated_decrypt_update(std::move(ciphertext), alignment);
255 }
256 }
257
258 // we need to ensure the tag is stored in continuous memory.
259 if (1 != EVP_CIPHER_CTX_ctrl(ectx.get(), EVP_CTRL_GCM_SET_TAG,
260 AESGCM_TAG_LEN, auth_tag.c_str())) {
261 throw std::runtime_error("EVP_CIPHER_CTX_ctrl failed");
262 }
263
264 // I expect that 0 bytes will be appended. The call is supposed solely to
265 // authenticate the message.
266 {
267 int final_len = 0;
268 if (0 >= EVP_DecryptFinal_ex(ectx.get(), nullptr, &final_len)) {
269 ldout(cct, 15) << __func__
270 << " plainbl.length()=" << plainbl.length()
271 << " final_len=" << final_len
272 << dendl;
273 throw MsgAuthError();
274 } else {
275 ceph_assert_always(final_len == 0);
276 ceph_assert_always(plainbl.length() + final_len + AESGCM_TAG_LEN == cnt_len);
277 }
278 }
279
280 return plainbl;
281}
282
283ceph::crypto::onwire::rxtx_t ceph::crypto::onwire::rxtx_t::create_handler_pair(
284 CephContext* cct,
285 const AuthConnectionMeta& auth_meta,
286 bool crossed)
287{
288 if (auth_meta.is_mode_secure()) {
289 ceph_assert_always(auth_meta.connection_secret.length() >= \
290 sizeof(key_t) + 2 * sizeof(nonce_t));
291 const char* secbuf = auth_meta.connection_secret.c_str();
292
293 key_t key;
294 {
295 ::memcpy(key.data(), secbuf, sizeof(key));
296 secbuf += sizeof(key);
297 }
298
299 nonce_t rx_nonce;
300 {
301 ::memcpy(&rx_nonce, secbuf, sizeof(rx_nonce));
302 secbuf += sizeof(rx_nonce);
303 }
304
305 nonce_t tx_nonce;
306 {
307 ::memcpy(&tx_nonce, secbuf, sizeof(tx_nonce));
308 secbuf += sizeof(tx_nonce);
309 }
310
311 return {
312 std::make_unique<AES128GCM_OnWireRxHandler>(
313 cct, key, crossed ? tx_nonce : rx_nonce),
314 std::make_unique<AES128GCM_OnWireTxHandler>(
315 cct, key, crossed ? rx_nonce : tx_nonce)
316 };
317 } else {
318 return { nullptr, nullptr };
319 }
320}
321
322} // namespace ceph::crypto::onwire