fizz/record/EncryptedRecordLayer.cpp (209 lines of code) (raw):

/* * Copyright (c) 2018-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #include <fizz/crypto/aead/IOBufUtil.h> #include <fizz/record/EncryptedRecordLayer.h> namespace fizz { using ContentTypeType = typename std::underlying_type<ContentType>::type; using ProtocolVersionType = typename std::underlying_type<ProtocolVersion>::type; static constexpr uint16_t kMaxEncryptedRecordSize = 0x4000 + 256; // 16k + 256 static constexpr size_t kEncryptedHeaderSize = sizeof(ContentType) + sizeof(ProtocolVersion) + sizeof(uint16_t); EncryptedReadRecordLayer::ReadResult<Buf> EncryptedReadRecordLayer::getDecryptedBuf( folly::IOBufQueue& buf, Aead::AeadOptions options) { while (true) { // Cache the front buffer, calling front may invoke and update // of the tail cache. auto frontBuf = buf.front(); folly::io::Cursor cursor(frontBuf); if (buf.empty() || !cursor.canAdvance(kEncryptedHeaderSize)) { return ReadResult<Buf>::noneWithSizeHint( kEncryptedHeaderSize - buf.chainLength()); } std::array<uint8_t, kEncryptedHeaderSize> ad; folly::io::Cursor adCursor(cursor); adCursor.pull(ad.data(), ad.size()); folly::IOBuf adBuf{folly::IOBuf::wrapBufferAsValue(folly::range(ad))}; auto contentType = static_cast<ContentType>(cursor.readBE<ContentTypeType>()); cursor.skip(sizeof(ProtocolVersion)); auto length = cursor.readBE<uint16_t>(); if (length == 0) { throw std::runtime_error("received 0 length encrypted record"); } if (length > kMaxEncryptedRecordSize) { throw std::runtime_error("received too long encrypted record"); } auto consumedBytes = cursor - frontBuf; if (buf.chainLength() < consumedBytes + length) { auto remaining = (consumedBytes + length) - buf.chainLength(); return ReadResult<Buf>::noneWithSizeHint(remaining); } if (contentType == ContentType::alert && length == 2) { auto alert = decode<Alert>(cursor); throw std::runtime_error(folly::to<std::string>( "received plaintext alert in encrypted record: ", toString(alert.description))); } // If we already know that the length of the buffer is the // same as the number of bytes we need, move the entire buffer. std::unique_ptr<folly::IOBuf> encrypted; if (buf.chainLength() == consumedBytes + length) { encrypted = buf.move(); } else { encrypted = buf.split(consumedBytes + length); } trimStart(*encrypted, consumedBytes); if (contentType == ContentType::change_cipher_spec) { encrypted->coalesce(); if (encrypted->length() == 1 && *encrypted->data() == 0x01) { continue; } else { throw FizzException( "received ccs", AlertDescription::illegal_parameter); } } if (seqNum_ == std::numeric_limits<uint64_t>::max()) { throw std::runtime_error("max read seq num"); } if (skipFailedDecryption_) { auto decryptAttempt = aead_->tryDecrypt( std::move(encrypted), useAdditionalData_ ? &adBuf : nullptr, seqNum_, options); if (decryptAttempt) { seqNum_++; skipFailedDecryption_ = false; return ReadResult<Buf>::from(std::move(decryptAttempt).value()); } else { continue; } } else { return ReadResult<Buf>::from(aead_->decrypt( std::move(encrypted), useAdditionalData_ ? &adBuf : nullptr, seqNum_++, options)); } } } EncryptedReadRecordLayer::ReadResult<TLSMessage> EncryptedReadRecordLayer::read( folly::IOBufQueue& buf, Aead::AeadOptions options) { auto decryptedBuf = getDecryptedBuf(buf, std::move(options)); if (!decryptedBuf) { return ReadResult<TLSMessage>::noneWithSizeHint(decryptedBuf.sizeHint); } TLSMessage msg{}; // Iterate over the buffers while trying to find // the first non-zero octet. This is much faster than // first iterating and then trimming. auto currentBuf = decryptedBuf->get(); bool nonZeroFound = false; do { currentBuf = currentBuf->prev(); size_t i = currentBuf->length(); while (i > 0 && !nonZeroFound) { nonZeroFound = (currentBuf->data()[i - 1] != 0); i--; } if (nonZeroFound) { msg.type = static_cast<ContentType>(currentBuf->data()[i]); } currentBuf->trimEnd(currentBuf->length() - i); } while (!nonZeroFound && currentBuf != decryptedBuf->get()); if (!nonZeroFound) { throw std::runtime_error("No content type found"); } msg.fragment = std::move(*decryptedBuf); switch (msg.type) { case ContentType::handshake: case ContentType::alert: case ContentType::application_data: break; default: throw std::runtime_error(folly::to<std::string>( "received encrypted content type ", static_cast<ContentTypeType>(msg.type))); } if (!msg.fragment || msg.fragment->empty()) { if (msg.type == ContentType::application_data) { msg.fragment = folly::IOBuf::create(0); } else { throw std::runtime_error("received empty fragment"); } } return ReadResult<TLSMessage>::from(std::move(msg)); } EncryptionLevel EncryptedReadRecordLayer::getEncryptionLevel() const { return encryptionLevel_; } TLSContent EncryptedWriteRecordLayer::write( TLSMessage&& msg, Aead::AeadOptions options) const { folly::IOBufQueue queue; queue.append(std::move(msg.fragment)); std::unique_ptr<folly::IOBuf> outBuf; std::array<uint8_t, kEncryptedHeaderSize> headerBuf; auto header = folly::IOBuf::wrapBufferAsValue(folly::range(headerBuf)); aead_->setEncryptedBufferHeadroom(kEncryptedHeaderSize); while (!queue.empty()) { Buf dataBuf; uint16_t paddingSize; std::tie(dataBuf, paddingSize) = bufAndPaddingPolicy_->getBufAndPaddingToEncrypt(queue, maxRecord_); // check if we have enough room to add padding and the encrypted footer. if (!dataBuf->isShared() && dataBuf->prev()->tailroom() >= sizeof(ContentType) + paddingSize) { // extend it and add padding and footer folly::io::Appender appender(dataBuf.get(), 0); appender.writeBE(static_cast<ContentTypeType>(msg.type)); memset(appender.writableData(), 0, paddingSize); appender.append(paddingSize); } else { // not enough or shared - let's add enough for the tag as well auto encryptedFooter = folly::IOBuf::create( sizeof(ContentType) + paddingSize + aead_->getCipherOverhead()); folly::io::Appender appender(encryptedFooter.get(), 0); appender.writeBE(static_cast<ContentTypeType>(msg.type)); memset(appender.writableData(), 0, paddingSize); appender.append(paddingSize); dataBuf->prependChain(std::move(encryptedFooter)); } if (seqNum_ == std::numeric_limits<uint64_t>::max()) { throw std::runtime_error("max write seq num"); } // we will either be able to memcpy directly into the ciphertext or // need to create a new buf to insert before the ciphertext but we need // it for additional data header.clear(); folly::io::Appender appender(&header, 0); appender.writeBE( static_cast<ContentTypeType>(ContentType::application_data)); appender.writeBE( static_cast<ProtocolVersionType>(ProtocolVersion::tls_1_2)); auto ciphertextLength = dataBuf->computeChainDataLength() + aead_->getCipherOverhead(); appender.writeBE<uint16_t>(ciphertextLength); auto cipherText = aead_->encrypt( std::move(dataBuf), useAdditionalData_ ? &header : nullptr, seqNum_++, options); std::unique_ptr<folly::IOBuf> record; if (!cipherText->isShared() && cipherText->headroom() >= kEncryptedHeaderSize) { // prepend and then write it in cipherText->prepend(kEncryptedHeaderSize); memcpy(cipherText->writableData(), header.data(), header.length()); record = std::move(cipherText); } else { record = folly::IOBuf::copyBuffer(header.data(), header.length()); record->prependChain(std::move(cipherText)); } if (!outBuf) { outBuf = std::move(record); } else { outBuf->prependChain(std::move(record)); } } if (!outBuf) { outBuf = folly::IOBuf::create(0); } TLSContent content; content.data = std::move(outBuf); content.contentType = msg.type; content.encryptionLevel = encryptionLevel_; return content; } EncryptionLevel EncryptedWriteRecordLayer::getEncryptionLevel() const { return encryptionLevel_; } } // namespace fizz