proxygen
OpenSSLEVPCipher.cpp
Go to the documentation of this file.
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 
10 
11 #include <functional>
12 
13 namespace fizz {
14 namespace detail {
15 
16 void encFunc(EVP_CIPHER_CTX*, const folly::IOBuf&, folly::IOBuf&);
17 void encFuncBlocks(EVP_CIPHER_CTX*, const folly::IOBuf&, folly::IOBuf&);
18 
19 bool decFunc(
20  EVP_CIPHER_CTX*,
21  const folly::IOBuf&,
22  folly::IOBuf&,
24 bool decFuncBlocks(
25  EVP_CIPHER_CTX*,
26  const folly::IOBuf&,
27  folly::IOBuf&,
29 
31  EVP_CIPHER_CTX* encryptCtx,
32  const folly::IOBuf& plaintext,
34  size_t totalWritten = 0;
35  size_t totalInput = 0;
36  int outLen = 0;
37  auto outputCursor = transformBufferBlocks<16>(
38  plaintext,
39  output,
40  [&](uint8_t* cipher, const uint8_t* plain, size_t len) {
41  if (len > std::numeric_limits<int>::max()) {
42  throw std::runtime_error("Encryption error: too much plain text");
43  }
44  if (EVP_EncryptUpdate(
45  encryptCtx, cipher, &outLen, plain, static_cast<int>(len)) !=
46  1 ||
47  outLen < 0) {
48  throw std::runtime_error("Encryption error");
49  }
50  totalWritten += outLen;
51  totalInput += len;
52  return static_cast<size_t>(outLen);
53  });
54 
55  // We might end up needing to write more in the final encrypt stage
56  auto numBuffered = totalInput - totalWritten;
57  auto numLeftInOutput = outputCursor.length();
58  if (numBuffered <= numLeftInOutput) {
59  if (EVP_EncryptFinal_ex(encryptCtx, outputCursor.writableData(), &outLen) !=
60  1) {
61  throw std::runtime_error("Encryption error");
62  }
63  } else {
64  // we need to copy nicely - this should be at most one block
65  std::array<uint8_t, 16> block = {};
66  if (EVP_EncryptFinal_ex(encryptCtx, block.data(), &outLen) != 1) {
67  throw std::runtime_error("Encryption error");
68  }
69  outputCursor.push(block.data(), outLen);
70  }
71 }
72 
73 void encFunc(
74  EVP_CIPHER_CTX* encryptCtx,
75  const folly::IOBuf& plaintext,
77  int numWritten = 0;
78  int outLen = 0;
80  plaintext,
81  output,
82  [&](uint8_t* cipher, const uint8_t* plain, size_t len) {
83  if (len > std::numeric_limits<int>::max()) {
84  throw std::runtime_error("Encryption error: too much plain text");
85  }
86  if (EVP_EncryptUpdate(
87  encryptCtx, cipher, &outLen, plain, static_cast<int>(len)) !=
88  1) {
89  throw std::runtime_error("Encryption error");
90  }
91  numWritten += outLen;
92  });
93  // We don't expect any writes at the end
94  if (EVP_EncryptFinal_ex(
95  encryptCtx, output.writableData() + numWritten, &outLen) != 1) {
96  throw std::runtime_error("Encryption error");
97  }
98 }
99 
101  EVP_CIPHER_CTX* decryptCtx,
102  const folly::IOBuf& ciphertext,
104  folly::MutableByteRange tagOut) {
105  if (EVP_CIPHER_CTX_ctrl(
106  decryptCtx,
107  EVP_CTRL_GCM_SET_TAG,
108  tagOut.size(),
109  static_cast<void*>(tagOut.begin())) != 1) {
110  throw std::runtime_error("Decryption error");
111  }
112 
113  size_t totalWritten = 0;
114  size_t totalInput = 0;
115  int outLen = 0;
116  auto outputCursor = transformBufferBlocks<16>(
117  ciphertext,
118  output,
119  [&](uint8_t* plain, const uint8_t* cipher, size_t len) {
120  if (len > std::numeric_limits<int>::max()) {
121  throw std::runtime_error("Decryption error: too much cipher text");
122  }
123  if (EVP_DecryptUpdate(
124  decryptCtx, plain, &outLen, cipher, static_cast<int>(len)) !=
125  1) {
126  throw std::runtime_error("Decryption error");
127  }
128  totalWritten += outLen;
129  totalInput += len;
130  return static_cast<size_t>(outLen);
131  });
132 
133  // We might end up needing to write more in the final encrypt stage
134  auto numBuffered = totalInput - totalWritten;
135  auto numLeftInOutput = outputCursor.length();
136  if (numBuffered <= numLeftInOutput) {
137  auto res =
138  EVP_DecryptFinal_ex(decryptCtx, outputCursor.writableData(), &outLen);
139  return res == 1;
140  } else {
141  // we need to copy nicely - this should be at most one block
142  std::array<uint8_t, 16> block = {};
143  auto res = EVP_DecryptFinal_ex(decryptCtx, block.data(), &outLen);
144  if (res != 1) {
145  return false;
146  }
147  outputCursor.push(block.data(), outLen);
148  return true;
149  }
150 }
151 
152 bool decFunc(
153  EVP_CIPHER_CTX* decryptCtx,
154  const folly::IOBuf& ciphertext,
156  folly::MutableByteRange tagOut) {
157  int numWritten = 0;
158  int outLen = 0;
160  ciphertext,
161  output,
162  [&](uint8_t* plain, const uint8_t* cipher, size_t len) {
163  if (len > std::numeric_limits<int>::max()) {
164  throw std::runtime_error("Decryption error: too much cipher text");
165  }
166  if (EVP_DecryptUpdate(
167  decryptCtx, plain, &outLen, cipher, static_cast<int>(len)) !=
168  1) {
169  throw std::runtime_error("Decryption error");
170  }
171  numWritten += outLen;
172  });
173 
174  auto tagLen = tagOut.size();
175  if (EVP_CIPHER_CTX_ctrl(
176  decryptCtx,
177  EVP_CTRL_GCM_SET_TAG,
178  tagLen,
179  static_cast<void*>(tagOut.begin())) != 1) {
180  throw std::runtime_error("Decryption error");
181  }
182  return EVP_DecryptFinal_ex(
183  decryptCtx, output.writableData() + numWritten, &outLen) == 1;
184 }
185 
186 std::unique_ptr<folly::IOBuf> evpEncrypt(
187  std::unique_ptr<folly::IOBuf>&& plaintext,
188  const folly::IOBuf* associatedData,
189  folly::ByteRange iv,
190  size_t tagLen,
191  bool useBlockOps,
192  size_t headroom,
193  EVP_CIPHER_CTX* encryptCtx) {
194  auto inputLength = plaintext->computeChainDataLength();
195  // Setup input and output buffers.
196  std::unique_ptr<folly::IOBuf> output;
197  folly::IOBuf* input;
198 
199  if (plaintext->isShared()) {
200  // create enough to also fit the tag and headroom
201  output = folly::IOBuf::create(headroom + inputLength + tagLen);
202  output->advance(headroom);
203  output->append(inputLength);
204  input = plaintext.get();
205  } else {
206  output = std::move(plaintext);
207  input = output.get();
208  }
209 
210  if (EVP_EncryptInit_ex(encryptCtx, nullptr, nullptr, nullptr, iv.data()) !=
211  1) {
212  throw std::runtime_error("Encryption error");
213  }
214 
215  if (associatedData) {
216  for (auto current : *associatedData) {
217  if (current.size() > std::numeric_limits<int>::max()) {
218  throw std::runtime_error("too much associated data");
219  }
220  int len;
221  if (EVP_EncryptUpdate(
222  encryptCtx,
223  nullptr,
224  &len,
225  current.data(),
226  static_cast<int>(current.size())) != 1) {
227  throw std::runtime_error("Encryption error");
228  }
229  }
230  }
231 
232  if (useBlockOps) {
233  encFuncBlocks(encryptCtx, *input, *output);
234  } else {
235  encFunc(encryptCtx, *input, *output);
236  }
237 
238  // output is always something we can modify
239  auto tailRoom = output->prev()->tailroom();
240  if (tailRoom < tagLen) {
241  std::unique_ptr<folly::IOBuf> tag = folly::IOBuf::create(tagLen);
242  tag->append(tagLen);
243  if (EVP_CIPHER_CTX_ctrl(
244  encryptCtx, EVP_CTRL_GCM_GET_TAG, tagLen, tag->writableData()) !=
245  1) {
246  throw std::runtime_error("Encryption error");
247  }
248  output->prependChain(std::move(tag));
249  } else {
250  auto lastBuf = output->prev();
251  lastBuf->append(tagLen);
252  // we can copy into output directly
253  if (EVP_CIPHER_CTX_ctrl(
254  encryptCtx,
255  EVP_CTRL_GCM_GET_TAG,
256  tagLen,
257  lastBuf->writableTail() - tagLen) != 1) {
258  throw std::runtime_error("Encryption error");
259  }
260  }
261  return output;
262 }
263 
265  std::unique_ptr<folly::IOBuf>&& ciphertext,
266  const folly::IOBuf* associatedData,
267  folly::ByteRange iv,
269  bool useBlockOps,
270  EVP_CIPHER_CTX* decryptCtx) {
271  auto tagLen = tagOut.size();
272  auto inputLength = ciphertext->computeChainDataLength();
273  if (inputLength < tagLen) {
274  return folly::none;
275  }
276  inputLength -= tagLen;
277 
278  folly::IOBuf* input;
279  std::unique_ptr<folly::IOBuf> output;
280  trimBytes(*ciphertext, tagOut);
281  if (ciphertext->isShared()) {
282  // If in is shared, then we have to make a copy of it.
283  output = folly::IOBuf::create(inputLength);
284  output->append(inputLength);
285  input = ciphertext.get();
286  } else {
287  // If in is not shared we can do decryption in-place.
288  output = std::move(ciphertext);
289  input = output.get();
290  }
291 
292  if (EVP_DecryptInit_ex(decryptCtx, nullptr, nullptr, nullptr, iv.data()) !=
293  1) {
294  throw std::runtime_error("Decryption error");
295  }
296 
297  if (associatedData) {
298  for (auto current : *associatedData) {
299  if (current.size() > std::numeric_limits<int>::max()) {
300  throw std::runtime_error("too much associated data");
301  }
302  int len;
303  if (EVP_DecryptUpdate(
304  decryptCtx,
305  nullptr,
306  &len,
307  current.data(),
308  static_cast<int>(current.size())) != 1) {
309  throw std::runtime_error("Decryption error");
310  }
311  }
312  }
313 
314  auto decrypted = useBlockOps
315  ? decFuncBlocks(decryptCtx, *input, *output, tagOut)
316  : decFunc(decryptCtx, *input, *output, tagOut);
317  if (!decrypted) {
318  return folly::none;
319  }
320  return std::move(output);
321 }
322 } // namespace detail
323 } // namespace fizz
bool decFuncBlocks(EVP_CIPHER_CTX *, const folly::IOBuf &, folly::IOBuf &, folly::MutableByteRange)
static std::unique_ptr< IOBuf > create(std::size_t capacity)
Definition: IOBuf.cpp:229
LogLevel max
Definition: LogLevel.cpp:31
void encFunc(EVP_CIPHER_CTX *, const folly::IOBuf &, folly::IOBuf &)
void trimBytes(IOBuf &buf, folly::MutableByteRange trimmed)
Definition: IOBufUtil.cpp:15
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
constexpr size_type size() const
Definition: Range.h:431
void advance(std::size_t amount)
Definition: IOBuf.h:632
std::size_t tailroom() const
Definition: IOBuf.h:551
CipherSuite cipher
int current
uint8_t * writableData()
Definition: IOBuf.h:509
constexpr Iter data() const
Definition: Range.h:446
void transformBuffer(const folly::IOBuf &in, folly::IOBuf &out, Func func)
Definition: IOBufUtil.h:38
Definition: Actions.h:16
void prependChain(std::unique_ptr< IOBuf > &&iobuf)
Definition: IOBuf.cpp:509
IOBuf * prev()
Definition: IOBuf.h:610
constexpr Iter begin() const
Definition: Range.h:452
bool decFunc(EVP_CIPHER_CTX *, const folly::IOBuf &, folly::IOBuf &, folly::MutableByteRange)
std::unique_ptr< folly::IOBuf > 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)
folly::Optional< std::unique_ptr< folly::IOBuf > > evpDecrypt(std::unique_ptr< folly::IOBuf > &&ciphertext, const folly::IOBuf *associatedData, folly::ByteRange iv, folly::MutableByteRange tag, bool useBlockOps, EVP_CIPHER_CTX *decryptCtx)
void append(std::size_t amount)
Definition: IOBuf.h:689
constexpr None none
Definition: Optional.h:87
void encFuncBlocks(EVP_CIPHER_CTX *, const folly::IOBuf &, folly::IOBuf &)