Skip to content

Commit

Permalink
Add core dump protection to AES-CTR HMAC based Streaming AEAD
Browse files Browse the repository at this point in the history
This has an internal-only effect and it is a noop in open source.

PiperOrigin-RevId: 717456686
Change-Id: I540ff75fcace18606e9ef469e85bc4e283263889
  • Loading branch information
morambro authored and copybara-github committed Jan 20, 2025
1 parent 455f132 commit bc24740
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 50 deletions.
3 changes: 2 additions & 1 deletion tink/subtle/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -521,16 +521,17 @@ cc_library(
":subtle_util",
"//tink:mac",
"//tink/internal:aes_util",
"//tink/internal:dfsan_forwarders",
"//tink/internal:fips_utils",
"//tink/internal:ssl_unique_ptr",
"//tink/util:errors",
"//tink/util:secret_data",
"//tink/util:status",
"//tink/util:statusor",
"@boringssl//:crypto",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)

Expand Down
3 changes: 2 additions & 1 deletion tink/subtle/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,13 @@ tink_cc_library(
absl::memory
absl::status
absl::strings
absl::span
crypto
tink::core::mac
tink::internal::aes_util
tink::internal::dfsan_forwarders
tink::internal::fips_utils
tink::internal::ssl_unique_ptr
tink::util::errors
tink::util::secret_data
tink::util::status
tink::util::statusor
Expand Down
182 changes: 134 additions & 48 deletions tink/subtle/aes_ctr_hmac_streaming.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "openssl/err.h"
#include "openssl/evp.h"
#include "tink/internal/aes_util.h"
#include "tink/internal/dfsan_forwarders.h"
#include "tink/internal/fips_utils.h"
#include "tink/internal/ssl_unique_ptr.h"
#include "tink/subtle/common_enums.h"
Expand All @@ -41,7 +43,6 @@
#include "tink/subtle/stream_segment_decrypter.h"
#include "tink/subtle/stream_segment_encrypter.h"
#include "tink/subtle/subtle_util.h"
#include "tink/util/errors.h"
#include "tink/util/secret_data.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
Expand All @@ -50,6 +51,10 @@ namespace crypto {
namespace tink {
namespace subtle {

using ::crypto::tink::internal::CallWithCoreDumpProtection;
using ::crypto::tink::internal::DfsanClearLabel;
using ::crypto::tink::internal::ScopedAssumeRegionCoreDumpSafe;

static std::string NonceForSegment(absl::string_view nonce_prefix,
int64_t segment_number,
bool is_last_segment) {
Expand Down Expand Up @@ -191,6 +196,66 @@ AesCtrHmacStreamSegmentEncrypter::New(const AesCtrHmacStreaming::Params& params,
*cipher, std::move(mac)))};
}

namespace {

util::Status EncryptSensitive(const util::SecretData& key,
const EVP_CIPHER& cipher, absl::string_view nonce,
absl::string_view plaintext,
absl::Span<char> ciphertext) {
internal::SslUniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
if (ctx == nullptr) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize EVP_CIPHER_CTX");
}
if (EVP_EncryptInit_ex(ctx.get(), &cipher, nullptr /* engine */,
reinterpret_cast<const uint8_t*>(key.data()),
reinterpret_cast<const uint8_t*>(nonce.data())) != 1) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize ctx");
}

int out_len;
const uint8_t* plaintext_data =
reinterpret_cast<const uint8_t*>(plaintext.data());
uint8_t* ciphertext_data = reinterpret_cast<uint8_t*>(ciphertext.data());
if (EVP_EncryptUpdate(ctx.get(), ciphertext_data, &out_len, plaintext_data,
plaintext.size()) != 1) {
return util::Status(absl::StatusCode::kInternal, "encryption failed");
}
if (out_len != plaintext.size()) {
return util::Status(absl::StatusCode::kInternal,
"incorrect ciphertext size");
}

return util::OkStatus();
}

util::Status Encrypt(const util::SecretData& key, const EVP_CIPHER& cipher,
absl::string_view nonce, absl::string_view plaintext,
absl::Span<char> ciphertext) {
// The ciphertext will be fine to leak. This assumes that BoringSSL does not
// use the memory as scratch pad and writes sensitive data into it.
ScopedAssumeRegionCoreDumpSafe scope_object(ciphertext.data(),
ciphertext.size());
util::Status status = CallWithCoreDumpProtection([&]() {
return EncryptSensitive(
key, cipher, nonce,
absl::string_view(reinterpret_cast<const char*>(plaintext.data()),
plaintext.size()),
absl::MakeSpan(reinterpret_cast<char*>(ciphertext.data()),
ciphertext.size()));
});
if (!status.ok()) {
return status;
}
// Declassify the ciphertext: it can depend on the key, but that's
// intentional.
DfsanClearLabel(ciphertext.data(), ciphertext.size());
return util::OkStatus();
}

} // namespace

util::Status AesCtrHmacStreamSegmentEncrypter::EncryptSegment(
const std::vector<uint8_t>& plaintext, bool is_last_segment,
std::vector<uint8_t>* ciphertext_buffer) {
Expand All @@ -216,26 +281,14 @@ util::Status AesCtrHmacStreamSegmentEncrypter::EncryptSegment(
NonceForSegment(nonce_prefix_, segment_number_, is_last_segment);

// Encrypt.
internal::SslUniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
if (ctx == nullptr) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize EVP_CIPHER_CTX");
}
if (EVP_EncryptInit_ex(ctx.get(), cipher_, nullptr /* engine */,
reinterpret_cast<const uint8_t*>(key_value_.data()),
reinterpret_cast<const uint8_t*>(nonce.data())) != 1) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize ctx");
}

int out_len;
if (EVP_EncryptUpdate(ctx.get(), ciphertext_buffer->data(), &out_len,
plaintext.data(), plaintext.size()) != 1) {
return util::Status(absl::StatusCode::kInternal, "encryption failed");
}
if (out_len != plaintext.size()) {
return util::Status(absl::StatusCode::kInternal,
"incorrect ciphertext size");
if (util::Status res = Encrypt(
key_value_, *cipher_, nonce,
absl::string_view(reinterpret_cast<const char*>(plaintext.data()),
plaintext.size()),
absl::MakeSpan(reinterpret_cast<char*>(ciphertext_buffer->data()),
plaintext.size()));
!res.ok()) {
return res;
}

// Add MAC tag.
Expand Down Expand Up @@ -309,6 +362,41 @@ util::Status AesCtrHmacStreamSegmentDecrypter::Init(
return util::OkStatus();
}

namespace {

util::Status DecryptSensitive(const util::SecretData& key,
const EVP_CIPHER& cipher, absl::string_view nonce,
absl::string_view ciphertext,
absl::Span<char> plaintext) {
// Decrypt.
internal::SslUniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
if (ctx.get() == nullptr) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize EVP_CIPHER_CTX");
}
if (EVP_DecryptInit_ex(ctx.get(), &cipher, nullptr /* engine */,
reinterpret_cast<const uint8_t*>(key.data()),
reinterpret_cast<const uint8_t*>(nonce.data())) != 1) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize ctx");
}
int out_len;
const uint8_t* ciphertext_data =
reinterpret_cast<const uint8_t*>(ciphertext.data());
uint8_t* plaintext_data = reinterpret_cast<uint8_t*>(plaintext.data());
if (EVP_DecryptUpdate(ctx.get(), plaintext_data, &out_len, ciphertext_data,
ciphertext.size()) != 1) {
return util::Status(absl::StatusCode::kInternal, "decryption failed");
}
if (out_len != plaintext.size()) {
return util::Status(absl::StatusCode::kInternal,
"incorrect plaintext size");
}
return util::OkStatus();
}

} // namespace

util::Status AesCtrHmacStreamSegmentDecrypter::DecryptSegment(
const std::vector<uint8_t>& ciphertext, int64_t segment_number,
bool is_last_segment, std::vector<uint8_t>* plaintext_buffer) {
Expand Down Expand Up @@ -342,36 +430,34 @@ util::Status AesCtrHmacStreamSegmentDecrypter::DecryptSegment(
NonceForSegment(nonce_prefix_, segment_number, is_last_segment);

// Verify MAC tag.
absl::string_view tag(
reinterpret_cast<const char*>(ciphertext.data() + pt_size), tag_size_);
absl::string_view ciphertext_string(
reinterpret_cast<const char*>(ciphertext.data()), pt_size);
auto status = mac_->VerifyMac(tag, absl::StrCat(nonce, ciphertext_string));
if (!status.ok()) return status;

// Decrypt.
internal::SslUniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
if (ctx.get() == nullptr) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize EVP_CIPHER_CTX");
}
if (EVP_DecryptInit_ex(ctx.get(), cipher_, nullptr /* engine */,
reinterpret_cast<const uint8_t*>(key_value_.data()),
reinterpret_cast<const uint8_t*>(nonce.data())) != 1) {
return util::Status(absl::StatusCode::kInternal,
"could not initialize ctx");
absl::string_view ciphertext_view(
reinterpret_cast<const char*>(ciphertext.data()), ciphertext.size());
absl::string_view tag = ciphertext_view.substr(pt_size);
absl::string_view ciphertext_string = ciphertext_view.substr(0, pt_size);
util::Status status =
mac_->VerifyMac(tag, absl::StrCat(nonce, ciphertext_string));
if (!status.ok()) {
return status;
}

int out_len;
if (EVP_DecryptUpdate(ctx.get(), plaintext_buffer->data(), &out_len,
ciphertext.data(), pt_size) != 1) {
return util::Status(absl::StatusCode::kInternal, "decryption failed");
// The following implies that the plaintext region is allowed to leak in core
// dumps.
ScopedAssumeRegionCoreDumpSafe scope_object(plaintext_buffer->data(),
plaintext_buffer->size());
if (util::Status status = CallWithCoreDumpProtection([&]() {
return DecryptSensitive(
key_value_, *cipher_, nonce,
absl::string_view(reinterpret_cast<const char*>(ciphertext.data()),
pt_size),
absl::MakeSpan(reinterpret_cast<char*>(plaintext_buffer->data()),
plaintext_buffer->size()));
});
!status.ok()) {
return status;
}
if (out_len != pt_size) {
return util::Status(absl::StatusCode::kInternal,
"incorrect plaintext size");
}

// Declassify the plaintext: it can depend on the key, but that's
// intentional.
DfsanClearLabel(plaintext_buffer->data(), plaintext_buffer->size());
return util::OkStatus();
}

Expand Down

0 comments on commit bc24740

Please sign in to comment.