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