proxygen
TLSTicketKeyManager.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
17 
18 #include <folly/Random.h>
19 #include <folly/String.h>
21 #include <openssl/aes.h>
22 #include <openssl/rand.h>
23 #include <openssl/ssl.h>
25 #include <wangle/ssl/SSLStats.h>
26 #include <wangle/ssl/SSLUtil.h>
27 
28 #ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
29 using std::string;
30 
31 namespace {
32 
33 const int kTLSTicketKeyNameLen = 4;
34 const int kTLSTicketKeySaltLen = 12;
35 
36 }
37 
38 namespace wangle {
39 
40 
41 // TLSTicketKeyManager Implementation
42 int32_t TLSTicketKeyManager::sExDataIndex_ = -1;
43 
44 TLSTicketKeyManager::TLSTicketKeyManager(
45  folly::SSLContext* ctx,
46  SSLStats* stats)
47  : ctx_(ctx), stats_(stats) {
48  SSLUtil::getSSLCtxExIndex(&sExDataIndex_);
49  SSL_CTX_set_ex_data(ctx_->getSSLCtx(), sExDataIndex_, this);
50 }
51 
52 TLSTicketKeyManager::~TLSTicketKeyManager() {
53 }
54 
55 int
56 TLSTicketKeyManager::callback(SSL* ssl, unsigned char* keyName,
57  unsigned char* iv,
58  EVP_CIPHER_CTX* cipherCtx,
59  HMAC_CTX* hmacCtx, int encrypt) {
60  TLSTicketKeyManager* manager = nullptr;
61  SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
62  manager = (TLSTicketKeyManager *)SSL_CTX_get_ex_data(ctx, sExDataIndex_);
63 
64  if (manager == nullptr) {
65  LOG(FATAL) << "Null TLSTicketKeyManager in callback" ;
66  }
67  return manager->processTicket(ssl, keyName, iv, cipherCtx, hmacCtx, encrypt);
68 }
69 
70 int
71 TLSTicketKeyManager::processTicket(SSL*, unsigned char* keyName,
72  unsigned char* iv,
73  EVP_CIPHER_CTX* cipherCtx,
74  HMAC_CTX* hmacCtx, int encrypt) {
75  uint8_t salt[kTLSTicketKeySaltLen];
76  uint8_t* saltptr = nullptr;
77  uint8_t output[SHA256_DIGEST_LENGTH];
78  uint8_t* hmacKey = nullptr;
79  uint8_t* aesKey = nullptr;
80  TLSTicketKeySource* key = nullptr;
81  int result = 0;
82 
83  if (encrypt) {
84  key = findEncryptionKey();
85  if (key == nullptr) {
86  // no keys available to encrypt
87  VLOG(2) << "No TLS ticket key found";
88  return -1;
89  }
90  VLOG(4) << "Encrypting new ticket with key name=" <<
91  SSLUtil::hexlify(key->keyName_);
92 
93  // Get a random salt and write out key name
94  if (RAND_bytes(salt, (int)sizeof(salt)) != 1 &&
95  ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_RAND) {
96  ERR_get_error();
97  }
98  memcpy(keyName, key->keyName_.data(), kTLSTicketKeyNameLen);
99  memcpy(keyName + kTLSTicketKeyNameLen, salt, kTLSTicketKeySaltLen);
100 
101  // Create the unique keys by hashing with the salt
102  makeUniqueKeys(key->keySource_, sizeof(key->keySource_), salt, output);
103  // This relies on the fact that SHA256 has 32 bytes of output
104  // and that AES-128 keys are 16 bytes
105  hmacKey = output;
106  aesKey = output + SHA256_DIGEST_LENGTH / 2;
107 
108  // Initialize iv and cipher/mac CTX
109  if (RAND_bytes(iv, AES_BLOCK_SIZE) != 1 &&
110  ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_RAND) {
111  ERR_get_error();
112  }
113  HMAC_Init_ex(hmacCtx, hmacKey, SHA256_DIGEST_LENGTH / 2,
114  EVP_sha256(), nullptr);
115  EVP_EncryptInit_ex(cipherCtx, EVP_aes_128_cbc(), nullptr, aesKey, iv);
116 
117  result = 1;
118  } else {
119  key = findDecryptionKey(keyName);
120  if (key == nullptr) {
121  // no ticket found for decryption - will issue a new ticket
122  if (VLOG_IS_ON(4)) {
123  string skeyName((char *)keyName, kTLSTicketKeyNameLen);
124  VLOG(4) << "Can't find ticket key with name=" <<
125  SSLUtil::hexlify(skeyName)<< ", will generate new ticket";
126  }
127 
128  result = 0;
129  } else {
130  VLOG(4) << "Decrypting ticket with key name=" <<
131  SSLUtil::hexlify(key->keyName_);
132 
133  // Reconstruct the unique key via the salt
134  saltptr = keyName + kTLSTicketKeyNameLen;
135  makeUniqueKeys(key->keySource_, sizeof(key->keySource_), saltptr, output);
136  hmacKey = output;
137  aesKey = output + SHA256_DIGEST_LENGTH / 2;
138 
139  // Initialize cipher/mac CTX
140  HMAC_Init_ex(hmacCtx, hmacKey, SHA256_DIGEST_LENGTH / 2,
141  EVP_sha256(), nullptr);
142  EVP_DecryptInit_ex(cipherCtx, EVP_aes_128_cbc(), nullptr, aesKey, iv);
143 
144  result = 1;
145  }
146  }
147  // result records whether a ticket key was found to decrypt this ticket,
148  // not wether the session was re-used.
149  if (stats_) {
150  stats_->recordTLSTicket(encrypt, result);
151  }
152 
153  return result;
154 }
155 
156 bool
157 TLSTicketKeyManager::setTLSTicketKeySeeds(
158  const std::vector<std::string>& oldSeeds,
159  const std::vector<std::string>& currentSeeds,
160  const std::vector<std::string>& newSeeds) {
161 
162  recordTlsTicketRotation(oldSeeds, currentSeeds, newSeeds);
163 
164  bool result = true;
165 
166  activeKeys_.clear();
167  ticketKeys_.clear();
168  ticketSeeds_.clear();
169  const std::vector<string> *seedList = &oldSeeds;
170  for (uint32_t i = 0; i < 3; i++) {
171  TLSTicketSeedType type = (TLSTicketSeedType)i;
172  if (type == SEED_CURRENT) {
173  seedList = &currentSeeds;
174  } else if (type == SEED_NEW) {
175  seedList = &newSeeds;
176  }
177 
178  for (const auto& seedInput: *seedList) {
179  TLSTicketSeed* seed = insertSeed(seedInput, type);
180  if (seed == nullptr) {
181  result = false;
182  continue;
183  }
184  insertNewKey(seed, 1, nullptr);
185  }
186  }
187  if (!result) {
188  VLOG(2) << "One or more seeds failed to decode";
189  }
190 
191  if (ticketKeys_.size() == 0 || activeKeys_.size() == 0) {
192  LOG(WARNING) << "No keys configured, falling back to default";
193  SSL_CTX_set_tlsext_ticket_key_cb(ctx_->getSSLCtx(), nullptr);
194  return false;
195  }
196  SSL_CTX_set_tlsext_ticket_key_cb(ctx_->getSSLCtx(),
197  TLSTicketKeyManager::callback);
198 
199  return true;
200 }
201 
202 bool
203 TLSTicketKeyManager::getTLSTicketKeySeeds(
204  std::vector<std::string>& oldSeeds,
205  std::vector<std::string>& currentSeeds,
206  std::vector<std::string>& newSeeds) const {
207  oldSeeds.clear();
208  currentSeeds.clear();
209  newSeeds.clear();
210  bool allGot = true;
211  for (const auto& seed : ticketSeeds_) {
212  std::string hexSeed;
213  if (!folly::hexlify(seed->seed_, hexSeed)) {
214  allGot = false;
215  continue;
216  }
217  if (seed->type_ == TLSTicketSeedType::SEED_OLD) {
218  oldSeeds.push_back(hexSeed);
219  } else if(seed->type_ == TLSTicketSeedType::SEED_CURRENT) {
220  currentSeeds.push_back(hexSeed);
221  } else {
222  newSeeds.push_back(hexSeed);
223  }
224  }
225  return allGot;
226 }
227 
228 void TLSTicketKeyManager::recordTlsTicketRotation(
229  const std::vector<std::string>& oldSeeds,
230  const std::vector<std::string>& currentSeeds,
231  const std::vector<std::string>& newSeeds) {
232  if (stats_) {
233  TLSTicketKeySeeds next{oldSeeds, currentSeeds, newSeeds};
234  TLSTicketKeySeeds current;
235  getTLSTicketKeySeeds(
236  current.oldSeeds, current.currentSeeds, current.newSeeds);
237  stats_->recordTLSTicketRotation(current.isValidRotation(next));
238  }
239 }
240 
241 string
242 TLSTicketKeyManager::makeKeyName(TLSTicketSeed* seed, uint32_t n,
243  unsigned char* nameBuf) {
244  SHA256_CTX ctx;
245 
246  SHA256_Init(&ctx);
247  SHA256_Update(&ctx, seed->seedName_, sizeof(seed->seedName_));
248  SHA256_Update(&ctx, &n, sizeof(n));
249  SHA256_Final(nameBuf, &ctx);
250  return string((char *)nameBuf, kTLSTicketKeyNameLen);
251 }
252 
253 TLSTicketKeyManager::TLSTicketKeySource*
254 TLSTicketKeyManager::insertNewKey(TLSTicketSeed* seed, uint32_t hashCount,
255  TLSTicketKeySource* prevKey) {
256  unsigned char nameBuf[SHA256_DIGEST_LENGTH];
257  std::unique_ptr<TLSTicketKeySource> newKey(new TLSTicketKeySource);
258 
259  // This function supports hash chaining but it is not currently used.
260 
261  if (prevKey != nullptr) {
262  hashNth(prevKey->keySource_, sizeof(prevKey->keySource_),
263  newKey->keySource_, 1);
264  } else {
265  // can't go backwards or the current is missing, start from the beginning
266  hashNth((unsigned char *)seed->seed_.data(), seed->seed_.length(),
267  newKey->keySource_, hashCount);
268  }
269 
270  newKey->hashCount_ = hashCount;
271  newKey->keyName_ = makeKeyName(seed, hashCount, nameBuf);
272  newKey->type_ = seed->type_;
273  auto newKeyName = newKey->keyName_;
274  auto it = ticketKeys_.insert(std::make_pair(std::move(newKeyName),
275  std::move(newKey)));
276 
277  auto key = it.first->second.get();
278  if (key->type_ == SEED_CURRENT) {
279  activeKeys_.push_back(key);
280  }
281  VLOG(4) << "Adding key for " << hashCount << " type=" <<
282  (uint32_t)key->type_ << " Name=" << SSLUtil::hexlify(key->keyName_);
283 
284  return key;
285 }
286 
287 void
288 TLSTicketKeyManager::hashNth(const unsigned char* input, size_t input_len,
289  unsigned char* output, uint32_t n) {
290  assert(n > 0);
291  for (uint32_t i = 0; i < n; i++) {
292  SHA256(input, input_len, output);
293  input = output;
294  input_len = SHA256_DIGEST_LENGTH;
295  }
296 }
297 
298 TLSTicketKeyManager::TLSTicketSeed *
299 TLSTicketKeyManager::insertSeed(const string& seedInput,
300  TLSTicketSeedType type) {
301  TLSTicketSeed* seed = nullptr;
302  string seedOutput;
303 
304  if (!folly::unhexlify<string, string>(seedInput, seedOutput)) {
305  LOG(WARNING) << "Failed to decode seed type=" << (uint32_t)type <<
306  " seed=" << seedInput;
307  return seed;
308  }
309 
310  seed = new TLSTicketSeed();
311  seed->seed_ = seedOutput;
312  seed->type_ = type;
313  SHA256((unsigned char *)seedOutput.data(), seedOutput.length(),
314  seed->seedName_);
315  ticketSeeds_.push_back(std::unique_ptr<TLSTicketSeed>(seed));
316 
317  return seed;
318 }
319 
320 TLSTicketKeyManager::TLSTicketKeySource *
321 TLSTicketKeyManager::findEncryptionKey() {
322  TLSTicketKeySource* result = nullptr;
323  // call to rand here is a bit hokey since it's not cryptographically
324  // random, and is predictably seeded with 0. However, activeKeys_
325  // is probably not going to have very many keys in it, and most
326  // likely only 1.
327  size_t numKeys = activeKeys_.size();
328  if (numKeys > 0) {
329  auto const i = numKeys == 1 ? 0ul : folly::Random::rand32(numKeys);
330  result = activeKeys_[i];
331  }
332  return result;
333 }
334 
335 TLSTicketKeyManager::TLSTicketKeySource *
336 TLSTicketKeyManager::findDecryptionKey(unsigned char* keyName) {
337  string name((char *)keyName, kTLSTicketKeyNameLen);
338  TLSTicketKeySource* key = nullptr;
339  TLSTicketKeyMap::iterator mapit = ticketKeys_.find(name);
340  if (mapit != ticketKeys_.end()) {
341  key = mapit->second.get();
342  }
343  return key;
344 }
345 
346 void
347 TLSTicketKeyManager::makeUniqueKeys(unsigned char* parentKey,
348  size_t keyLen,
349  unsigned char* salt,
350  unsigned char* output) {
351  SHA256_CTX hash_ctx;
352 
353  SHA256_Init(&hash_ctx);
354  SHA256_Update(&hash_ctx, parentKey, keyLen);
355  SHA256_Update(&hash_ctx, salt, kTLSTicketKeySaltLen);
356  SHA256_Final(output, &hash_ctx);
357 }
358 
359 } // namespace wangle
360 #endif
PskType type
static const int seed
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
static void getSSLCtxExIndex(int *pindex)
Definition: SSLUtil.h:74
std::shared_ptr< FizzServerContext > ctx_
int current
const char * name
Definition: http_parser.c:437
const char * string
Definition: Conv.cpp:212
static std::string hexlify(const std::string &binary)
Definition: SSLUtil.h:150
bool hexlify(const InputString &input, OutputString &output, bool append_output)
Definition: String-inl.h:596
static uint32_t rand32()
Definition: Random.h:213
def next(obj)
Definition: ast.py:58