1 /*
2 * Copyright (c) 2018-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9 #include <fizz/crypto/aead/OpenSSLEVPCipher.h>
10 #include <functional>
11
12 namespace fizz {
13
14 namespace {
15
encFuncBlocks(EVP_CIPHER_CTX * encryptCtx,const folly::IOBuf & plaintext,folly::IOBuf & output)16 void encFuncBlocks(
17 EVP_CIPHER_CTX* encryptCtx,
18 const folly::IOBuf& plaintext,
19 folly::IOBuf& output) {
20 size_t totalWritten = 0;
21 size_t totalInput = 0;
22 int outLen = 0;
23 auto outputCursor = transformBufferBlocks<16>(
24 plaintext,
25 output,
26 [&](uint8_t* cipher, const uint8_t* plain, size_t len) {
27 if (len > std::numeric_limits<int>::max()) {
28 throw std::runtime_error("Encryption error: too much plain text");
29 }
30 if (len == 0) {
31 return static_cast<size_t>(0);
32 }
33 if (EVP_EncryptUpdate(
34 encryptCtx, cipher, &outLen, plain, static_cast<int>(len)) !=
35 1 ||
36 outLen < 0) {
37 throw std::runtime_error("Encryption error");
38 }
39 totalWritten += outLen;
40 totalInput += len;
41 return static_cast<size_t>(outLen);
42 });
43
44 // We might end up needing to write more in the final encrypt stage
45 auto numBuffered = totalInput - totalWritten;
46 auto numLeftInOutput = outputCursor.length();
47 if (numBuffered <= numLeftInOutput) {
48 if (EVP_EncryptFinal_ex(encryptCtx, outputCursor.writableData(), &outLen) !=
49 1) {
50 throw std::runtime_error("Encryption error");
51 }
52 } else {
53 // we need to copy nicely - this should be at most one block
54 std::array<uint8_t, 16> block = {};
55 if (EVP_EncryptFinal_ex(encryptCtx, block.data(), &outLen) != 1) {
56 throw std::runtime_error("Encryption error");
57 }
58 outputCursor.push(block.data(), outLen);
59 }
60 }
61
encFunc(EVP_CIPHER_CTX * encryptCtx,const folly::IOBuf & plaintext,folly::IOBuf & output)62 void encFunc(
63 EVP_CIPHER_CTX* encryptCtx,
64 const folly::IOBuf& plaintext,
65 folly::IOBuf& output) {
66 int numWritten = 0;
67 int outLen = 0;
68 transformBuffer(
69 plaintext,
70 output,
71 [&](uint8_t* cipher, const uint8_t* plain, size_t len) {
72 if (len > std::numeric_limits<int>::max()) {
73 throw std::runtime_error("Encryption error: too much plain text");
74 }
75 if (len == 0) {
76 return;
77 }
78 if (EVP_EncryptUpdate(
79 encryptCtx, cipher, &outLen, plain, static_cast<int>(len)) !=
80 1) {
81 throw std::runtime_error("Encryption error");
82 }
83 numWritten += outLen;
84 });
85 // We don't expect any writes at the end
86 if (EVP_EncryptFinal_ex(
87 encryptCtx, output.writableData() + numWritten, &outLen) != 1) {
88 throw std::runtime_error("Encryption error");
89 }
90 }
91
decFuncBlocks(EVP_CIPHER_CTX * decryptCtx,const folly::IOBuf & ciphertext,folly::IOBuf & output,folly::MutableByteRange tagOut)92 bool decFuncBlocks(
93 EVP_CIPHER_CTX* decryptCtx,
94 const folly::IOBuf& ciphertext,
95 folly::IOBuf& output,
96 folly::MutableByteRange tagOut) {
97 if (EVP_CIPHER_CTX_ctrl(
98 decryptCtx,
99 EVP_CTRL_GCM_SET_TAG,
100 tagOut.size(),
101 static_cast<void*>(tagOut.begin())) != 1) {
102 throw std::runtime_error("Decryption error");
103 }
104
105 size_t totalWritten = 0;
106 size_t totalInput = 0;
107 int outLen = 0;
108 auto outputCursor = transformBufferBlocks<16>(
109 ciphertext,
110 output,
111 [&](uint8_t* plain, const uint8_t* cipher, size_t len) {
112 if (len > std::numeric_limits<int>::max()) {
113 throw std::runtime_error("Decryption error: too much cipher text");
114 }
115 if (EVP_DecryptUpdate(
116 decryptCtx, plain, &outLen, cipher, static_cast<int>(len)) !=
117 1) {
118 throw std::runtime_error("Decryption error");
119 }
120 totalWritten += outLen;
121 totalInput += len;
122 return static_cast<size_t>(outLen);
123 });
124
125 // We might end up needing to write more in the final encrypt stage
126 auto numBuffered = totalInput - totalWritten;
127 auto numLeftInOutput = outputCursor.length();
128 if (numBuffered <= numLeftInOutput) {
129 auto res =
130 EVP_DecryptFinal_ex(decryptCtx, outputCursor.writableData(), &outLen);
131 return res == 1;
132 } else {
133 // we need to copy nicely - this should be at most one block
134 std::array<uint8_t, 16> block = {};
135 auto res = EVP_DecryptFinal_ex(decryptCtx, block.data(), &outLen);
136 if (res != 1) {
137 return false;
138 }
139 outputCursor.push(block.data(), outLen);
140 return true;
141 }
142 }
143
decFunc(EVP_CIPHER_CTX * decryptCtx,const folly::IOBuf & ciphertext,folly::IOBuf & output,folly::MutableByteRange tagOut)144 bool decFunc(
145 EVP_CIPHER_CTX* decryptCtx,
146 const folly::IOBuf& ciphertext,
147 folly::IOBuf& output,
148 folly::MutableByteRange tagOut) {
149 int numWritten = 0;
150 int outLen = 0;
151 transformBuffer(
152 ciphertext,
153 output,
154 [&](uint8_t* plain, const uint8_t* cipher, size_t len) {
155 if (len > std::numeric_limits<int>::max()) {
156 throw std::runtime_error("Decryption error: too much cipher text");
157 }
158 if (EVP_DecryptUpdate(
159 decryptCtx, plain, &outLen, cipher, static_cast<int>(len)) !=
160 1) {
161 throw std::runtime_error("Decryption error");
162 }
163 numWritten += outLen;
164 });
165
166 auto tagLen = tagOut.size();
167 if (EVP_CIPHER_CTX_ctrl(
168 decryptCtx,
169 EVP_CTRL_GCM_SET_TAG,
170 tagLen,
171 static_cast<void*>(tagOut.begin())) != 1) {
172 throw std::runtime_error("Decryption error");
173 }
174 return EVP_DecryptFinal_ex(
175 decryptCtx, output.writableData() + numWritten, &outLen) == 1;
176 }
177
evpEncrypt(std::unique_ptr<folly::IOBuf> && plaintext,const folly::IOBuf * associatedData,folly::ByteRange iv,size_t tagLen,bool useBlockOps,size_t headroom,EVP_CIPHER_CTX * encryptCtx,Aead::AeadOptions options)178 std::unique_ptr<folly::IOBuf> evpEncrypt(
179 std::unique_ptr<folly::IOBuf>&& plaintext,
180 const folly::IOBuf* associatedData,
181 folly::ByteRange iv,
182 size_t tagLen,
183 bool useBlockOps,
184 size_t headroom,
185 EVP_CIPHER_CTX* encryptCtx,
186 Aead::AeadOptions options) {
187 auto inputLength = plaintext->computeChainDataLength();
188 const auto& bufOption = options.bufferOpt;
189 const auto& allocOption = options.allocOpt;
190 // Setup input and output buffers.
191 std::unique_ptr<folly::IOBuf> output;
192 folly::IOBuf* input;
193
194 // Whether to allow modifying buffer contents directly
195 bool allowInplaceEdit = !plaintext->isShared() ||
196 bufOption != Aead::BufferOption::RespectSharedPolicy;
197
198 // Whether to allow growing tailRoom
199 bool allowGrowth =
200 (bufOption ==
201 Aead::BufferOption::AllowFullModification || // When explicitly
202 // requested
203 !plaintext->isShared() || // When plaintext is unique
204 !allowInplaceEdit); // When not in-place (new buffer)
205
206 // Whether to allow memory allocations (throws if needs more memory)
207 bool allowAlloc = allocOption == Aead::AllocationOption::Allow;
208
209 if (!allowInplaceEdit && !allowAlloc) {
210 throw std::runtime_error(
211 "Cannot decrypt (must be in-place or allow allocation)");
212 }
213
214 if (allowInplaceEdit) {
215 output = std::move(plaintext);
216 input = output.get();
217 } else {
218 // create enough to also fit the tag and headroom
219 output = folly::IOBuf::create(headroom + inputLength + tagLen);
220 output->advance(headroom);
221 output->append(inputLength);
222 input = plaintext.get();
223 }
224
225 if (EVP_EncryptInit_ex(encryptCtx, nullptr, nullptr, nullptr, iv.data()) !=
226 1) {
227 throw std::runtime_error("Encryption error");
228 }
229
230 if (associatedData) {
231 for (auto current : *associatedData) {
232 if (current.size() > std::numeric_limits<int>::max()) {
233 throw std::runtime_error("too much associated data");
234 }
235 int len;
236 if (EVP_EncryptUpdate(
237 encryptCtx,
238 nullptr,
239 &len,
240 current.data(),
241 static_cast<int>(current.size())) != 1) {
242 throw std::runtime_error("Encryption error");
243 }
244 }
245 }
246
247 if (useBlockOps) {
248 encFuncBlocks(encryptCtx, *input, *output);
249 } else {
250 encFunc(encryptCtx, *input, *output);
251 }
252
253 // output is always something we can modify
254 auto tailRoom = output->prev()->tailroom();
255 if (tailRoom < tagLen || !allowGrowth) {
256 if (!allowAlloc) {
257 throw std::runtime_error("Cannot encrypt (insufficient space for tag)");
258 }
259 std::unique_ptr<folly::IOBuf> tag = folly::IOBuf::create(tagLen);
260 tag->append(tagLen);
261 if (EVP_CIPHER_CTX_ctrl(
262 encryptCtx, EVP_CTRL_GCM_GET_TAG, tagLen, tag->writableData()) !=
263 1) {
264 throw std::runtime_error("Encryption error");
265 }
266 output->prependChain(std::move(tag));
267 } else {
268 auto lastBuf = output->prev();
269 lastBuf->append(tagLen);
270 // we can copy into output directly
271 if (EVP_CIPHER_CTX_ctrl(
272 encryptCtx,
273 EVP_CTRL_GCM_GET_TAG,
274 tagLen,
275 lastBuf->writableTail() - tagLen) != 1) {
276 throw std::runtime_error("Encryption error");
277 }
278 }
279 return output;
280 }
281
evpDecrypt(std::unique_ptr<folly::IOBuf> && ciphertext,const folly::IOBuf * associatedData,folly::ByteRange iv,folly::MutableByteRange tagOut,bool useBlockOps,EVP_CIPHER_CTX * decryptCtx,bool inPlace)282 folly::Optional<std::unique_ptr<folly::IOBuf>> evpDecrypt(
283 std::unique_ptr<folly::IOBuf>&& ciphertext,
284 const folly::IOBuf* associatedData,
285 folly::ByteRange iv,
286 folly::MutableByteRange tagOut,
287 bool useBlockOps,
288 EVP_CIPHER_CTX* decryptCtx,
289 bool inPlace) {
290 auto inputLength = ciphertext->computeChainDataLength();
291
292 folly::IOBuf* input;
293 std::unique_ptr<folly::IOBuf> output;
294 // If not in-place, allocate buffers. Otherwise in and out are same.
295 if (!inPlace) {
296 output = folly::IOBuf::create(inputLength);
297 output->append(inputLength);
298 input = ciphertext.get();
299 } else {
300 output = std::move(ciphertext);
301 input = output.get();
302 }
303
304 if (EVP_DecryptInit_ex(decryptCtx, nullptr, nullptr, nullptr, iv.data()) !=
305 1) {
306 throw std::runtime_error("Decryption error");
307 }
308
309 if (associatedData) {
310 for (auto current : *associatedData) {
311 if (current.size() > std::numeric_limits<int>::max()) {
312 throw std::runtime_error("too much associated data");
313 }
314 int len;
315 if (EVP_DecryptUpdate(
316 decryptCtx,
317 nullptr,
318 &len,
319 current.data(),
320 static_cast<int>(current.size())) != 1) {
321 throw std::runtime_error("Decryption error");
322 }
323 }
324 }
325
326 auto decrypted = useBlockOps
327 ? decFuncBlocks(decryptCtx, *input, *output, tagOut)
328 : decFunc(decryptCtx, *input, *output, tagOut);
329 if (!decrypted) {
330 return folly::none;
331 }
332 return output;
333 }
334
335 } // namespace
336
OpenSSLEVPCipher(size_t keyLength,size_t ivLength,size_t tagLength,const EVP_CIPHER * cipher,bool operatesInBlocks,bool requiresPresetTagLen)337 OpenSSLEVPCipher::OpenSSLEVPCipher(
338 size_t keyLength,
339 size_t ivLength,
340 size_t tagLength,
341 const EVP_CIPHER* cipher,
342 bool operatesInBlocks,
343 bool requiresPresetTagLen)
344 : keyLength_(keyLength),
345 ivLength_(ivLength),
346 tagLength_(tagLength),
347 cipher_(cipher),
348 operatesInBlocks_(operatesInBlocks),
349 requiresPresetTagLen_(requiresPresetTagLen) {
350 encryptCtx_.reset(EVP_CIPHER_CTX_new());
351 if (encryptCtx_ == nullptr) {
352 throw std::runtime_error("Unable to allocate an EVP_CIPHER_CTX object");
353 }
354 decryptCtx_.reset(EVP_CIPHER_CTX_new());
355 if (decryptCtx_ == nullptr) {
356 throw std::runtime_error("Unable to allocate an EVP_CIPHER_CTX object");
357 }
358 if (EVP_EncryptInit_ex(
359 encryptCtx_.get(), cipher_, nullptr, nullptr, nullptr) != 1) {
360 throw std::runtime_error("Init error");
361 }
362 if (EVP_CIPHER_CTX_ctrl(
363 encryptCtx_.get(), EVP_CTRL_GCM_SET_IVLEN, ivLength_, nullptr) != 1) {
364 throw std::runtime_error("Error setting iv length");
365 }
366 if (EVP_DecryptInit_ex(
367 decryptCtx_.get(), cipher_, nullptr, nullptr, nullptr) != 1) {
368 throw std::runtime_error("Init error");
369 }
370 if (EVP_CIPHER_CTX_ctrl(
371 decryptCtx_.get(), EVP_CTRL_GCM_SET_IVLEN, ivLength_, nullptr) != 1) {
372 throw std::runtime_error("Error setting iv length");
373 }
374
375 if (requiresPresetTagLen_) {
376 if (EVP_CIPHER_CTX_ctrl(
377 encryptCtx_.get(), EVP_CTRL_GCM_SET_TAG, tagLength_, nullptr) !=
378 1) {
379 throw std::runtime_error("Error setting enc tag length");
380 }
381
382 if (EVP_CIPHER_CTX_ctrl(
383 decryptCtx_.get(), EVP_CTRL_GCM_SET_TAG, tagLength_, nullptr) !=
384 1) {
385 throw std::runtime_error("Error setting dec tag length");
386 }
387 }
388 }
389
setKey(TrafficKey trafficKey)390 void OpenSSLEVPCipher::setKey(TrafficKey trafficKey) {
391 trafficKey.key->coalesce();
392 trafficKey.iv->coalesce();
393 if (trafficKey.key->length() != keyLength_) {
394 throw std::runtime_error("Invalid key");
395 }
396 if (trafficKey.iv->length() != ivLength_) {
397 throw std::runtime_error("Invalid IV");
398 }
399 trafficKey_ = std::move(trafficKey);
400 // Cache the IV key. calling coalesce() is not free.
401 trafficIvKey_ = trafficKey_.iv->coalesce();
402 if (EVP_EncryptInit_ex(
403 encryptCtx_.get(),
404 nullptr,
405 nullptr,
406 trafficKey_.key->data(),
407 nullptr) != 1) {
408 throw std::runtime_error("Error setting encrypt key");
409 }
410 if (EVP_DecryptInit_ex(
411 decryptCtx_.get(),
412 nullptr,
413 nullptr,
414 trafficKey_.key->data(),
415 nullptr) != 1) {
416 throw std::runtime_error("Error setting decrypt key");
417 }
418 }
419
getKey() const420 folly::Optional<TrafficKey> OpenSSLEVPCipher::getKey() const {
421 if (!trafficKey_.key || !trafficKey_.iv) {
422 return folly::none;
423 }
424 return trafficKey_.clone();
425 }
426
encrypt(std::unique_ptr<folly::IOBuf> && plaintext,const folly::IOBuf * associatedData,uint64_t seqNum,Aead::AeadOptions options) const427 std::unique_ptr<folly::IOBuf> OpenSSLEVPCipher::encrypt(
428 std::unique_ptr<folly::IOBuf>&& plaintext,
429 const folly::IOBuf* associatedData,
430 uint64_t seqNum,
431 Aead::AeadOptions options) const {
432 auto iv = createIV(seqNum);
433 return evpEncrypt(
434 std::move(plaintext),
435 associatedData,
436 folly::ByteRange(iv.data(), ivLength_),
437 tagLength_,
438 operatesInBlocks_,
439 headroom_,
440 encryptCtx_.get(),
441 options);
442 }
443
inplaceEncrypt(std::unique_ptr<folly::IOBuf> && plaintext,const folly::IOBuf * associatedData,uint64_t seqNum) const444 std::unique_ptr<folly::IOBuf> OpenSSLEVPCipher::inplaceEncrypt(
445 std::unique_ptr<folly::IOBuf>&& plaintext,
446 const folly::IOBuf* associatedData,
447 uint64_t seqNum) const {
448 auto iv = createIV(seqNum);
449 return evpEncrypt(
450 std::move(plaintext),
451 associatedData,
452 folly::ByteRange(iv.data(), ivLength_),
453 tagLength_,
454 operatesInBlocks_,
455 headroom_,
456 encryptCtx_.get(),
457 {Aead::BufferOption::AllowFullModification,
458 Aead::AllocationOption::Deny});
459 }
460
tryDecrypt(std::unique_ptr<folly::IOBuf> && ciphertext,const folly::IOBuf * associatedData,uint64_t seqNum,Aead::AeadOptions options) const461 folly::Optional<std::unique_ptr<folly::IOBuf>> OpenSSLEVPCipher::tryDecrypt(
462 std::unique_ptr<folly::IOBuf>&& ciphertext,
463 const folly::IOBuf* associatedData,
464 uint64_t seqNum,
465 Aead::AeadOptions options) const {
466 // Check that there's enough data to decrypt
467 if (tagLength_ > ciphertext->computeChainDataLength()) {
468 return folly::none;
469 }
470
471 auto iv = createIV(seqNum);
472 auto inPlace =
473 (!ciphertext->isShared() ||
474 options.bufferOpt != Aead::BufferOption::RespectSharedPolicy);
475
476 if (!inPlace && options.allocOpt == Aead::AllocationOption::Deny) {
477 throw std::runtime_error("Unable to decrypt (no-alloc requires in-place)");
478 }
479
480 // Set up the tag buffer now
481 const auto& lastBuf = ciphertext->prev();
482 if (lastBuf->length() >= tagLength_) {
483 // We can directly carve out this buffer from the last IOBuf
484 auto tagBuf = lastBuf->cloneOne();
485 // Adjust buffer sizes
486 lastBuf->trimEnd(tagLength_);
487 tagBuf->trimStart(lastBuf->length());
488
489 folly::MutableByteRange tagOut{tagBuf->writableData(), tagLength_};
490 return evpDecrypt(
491 std::move(ciphertext),
492 associatedData,
493 folly::ByteRange(iv.data(), ivLength_),
494 tagOut,
495 operatesInBlocks_,
496 decryptCtx_.get(),
497 inPlace);
498 } else {
499 // Tag is fragmented so we need to copy it out.
500 if (options.allocOpt == Aead::AllocationOption::Deny) {
501 throw std::runtime_error(
502 "Unable to decrypt (tag is fragmented and no allocation allowed)");
503 }
504 std::array<uint8_t, kMaxTagLength> tag;
505 // buffer to copy the tag into when we decrypt
506 folly::MutableByteRange tagOut{tag.data(), tagLength_};
507 trimBytes(*ciphertext, tagOut);
508 return evpDecrypt(
509 std::move(ciphertext),
510 associatedData,
511 folly::ByteRange(iv.data(), ivLength_),
512 tagOut,
513 operatesInBlocks_,
514 decryptCtx_.get(),
515 inPlace);
516 }
517 }
518
getCipherOverhead() const519 size_t OpenSSLEVPCipher::getCipherOverhead() const {
520 return tagLength_;
521 }
522
createIV(uint64_t seqNum) const523 std::array<uint8_t, OpenSSLEVPCipher::kMaxIVLength> OpenSSLEVPCipher::createIV(
524 uint64_t seqNum) const {
525 std::array<uint8_t, kMaxIVLength> iv;
526 uint64_t bigEndianSeqNum = folly::Endian::big(seqNum);
527 const size_t prefixLength = ivLength_ - sizeof(uint64_t);
528 memset(iv.data(), 0, prefixLength);
529 memcpy(iv.data() + prefixLength, &bigEndianSeqNum, 8);
530 folly::MutableByteRange mutableIv{iv.data(), ivLength_};
531 XOR(trafficIvKey_, mutableIv);
532 return iv;
533 }
534 } // namespace fizz
535