Skip to content

Commit

Permalink
Switch AesGcmBoringSsl to EVP_AEAD.
Browse files Browse the repository at this point in the history
EVP_CIPHER is the legacy OpenSSL API that's kind of a disaster, hence recent
issues around null pointers and the like. New code should be written against
EVP_AEAD which is much more sensible. Notably, this file is about half as long
now.

On top of things, this should be more efficient as it allows BoringSSL to reuse
the AES key schedule. (EVP_AEAD_CTX is immutable after initialization. It can
used multiple times or concurrently or whatever.)

PiperOrigin-RevId: 211020450
GitOrigin-RevId: 0734d56e3432c24e9e75e78accefd6ae36eb9be4
  • Loading branch information
ise-crypto authored and chuckx committed Sep 5, 2018
1 parent e699fe1 commit 48d066c
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 164 deletions.
204 changes: 48 additions & 156 deletions cc/subtle/aes_gcm_boringssl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,198 +24,90 @@
#include "tink/util/errors.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "openssl/aead.h"
#include "openssl/err.h"
#include "openssl/evp.h"


namespace crypto {
namespace tink {
namespace subtle {

static const EVP_CIPHER* GetCipherForKeySize(uint32_t size_in_bytes) {
static const EVP_AEAD* GetAeadForKeySize(uint32_t size_in_bytes) {
switch (size_in_bytes) {
case 16:
return EVP_aes_128_gcm();
return EVP_aead_aes_128_gcm();
case 32:
return EVP_aes_256_gcm();
return EVP_aead_aes_256_gcm();
default:
return nullptr;
}
}

AesGcmBoringSsl::AesGcmBoringSsl(absl::string_view key_value,
const EVP_CIPHER* cipher)
: key_(key_value), cipher_(cipher) {}
util::Status AesGcmBoringSsl::Init(absl::string_view key_value) {
const EVP_AEAD* aead = GetAeadForKeySize(key_value.size());
if (aead == nullptr) {
return util::Status(util::error::INTERNAL, "invalid key size");
}
if (EVP_AEAD_CTX_init(
ctx_.get(), aead, reinterpret_cast<const uint8_t*>(key_value.data()),
key_value.size(), EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr) != 1) {
return util::Status(util::error::INTERNAL,
"could not initialize EVP_AEAD_CTX");
}
return util::OkStatus();
}

util::StatusOr<std::unique_ptr<Aead>> AesGcmBoringSsl::New(
absl::string_view key_value) {
const EVP_CIPHER* cipher = GetCipherForKeySize(key_value.size());
if (cipher == nullptr) {
return util::Status(util::error::INTERNAL, "invalid key size");
std::unique_ptr<AesGcmBoringSsl> aead(new AesGcmBoringSsl);
auto status = aead->Init(key_value);
if (!status.ok()) {
return status;
}
std::unique_ptr<Aead> aead(new AesGcmBoringSsl(key_value, cipher));
return std::move(aead);
return util::StatusOr<std::unique_ptr<Aead>>(std::move(aead));
}

util::StatusOr<std::string> AesGcmBoringSsl::Encrypt(
absl::string_view plaintext,
absl::string_view additional_data) const {
bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
// BoringSSL expects a non-null pointer for plaintext and additional_data,
// regardless of whether the size is 0.
if (plaintext.size() == 0 && plaintext.data() == nullptr) {
plaintext = absl::string_view("");
}
if (additional_data.size() == 0 && additional_data.data() == nullptr) {
additional_data = absl::string_view("");
}
if (ctx.get() == nullptr) {
return util::Status(util::error::INTERNAL,
"could not initialize EVP_CIPHER_CTX");
}
int ret = EVP_EncryptInit_ex(ctx.get(), cipher_, nullptr /* engine */,
nullptr /* key */, nullptr /* IV */);
if (ret != 1) {
return util::Status(util::error::INTERNAL, "EVP_EncryptInit_ex failed");
}
ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, IV_SIZE_IN_BYTES,
nullptr);
if (ret != 1) {
return util::Status(util::error::INTERNAL, "IV length not supported");
}
absl::string_view plaintext, absl::string_view additional_data) const {
const std::string iv = Random::GetRandomBytes(IV_SIZE_IN_BYTES);
ret = EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr,
reinterpret_cast<const uint8_t*>(key_.data()),
reinterpret_cast<const uint8_t*>(iv.data()));
if (ret != 1) {
return util::Status(util::error::INTERNAL, "Could not initialize ctx");
}
int len;
ret = EVP_EncryptUpdate(
ctx.get(), nullptr, &len,
reinterpret_cast<const uint8_t*>(additional_data.data()),
additional_data.size());
if (ret != 1) {
return util::Status(util::error::INTERNAL, "AAD is not supported");
}
size_t ciphertext_size = iv.size() + plaintext.size() + TAG_SIZE_IN_BYTES;
// TODO(bleichen): Check if it is OK to work on a std::string.
// This is unclear since some compiler may use copy-on-write.
// Allocates 1 byte more than necessary because we may potentially access
// &ct[ciphertext_size] causing vector range check error.
std::vector<uint8_t> ct(ciphertext_size + 1);
memcpy(&ct[0], reinterpret_cast<const uint8_t*>(iv.data()), iv.size());
size_t written = iv.size();
ret = EVP_EncryptUpdate(ctx.get(), &ct[written], &len,
reinterpret_cast<const uint8_t*>(plaintext.data()),
plaintext.size());
if (ret != 1) {
std::vector<uint8_t> ct(iv.size() + plaintext.size() + TAG_SIZE_IN_BYTES);
memcpy(ct.data(), iv.data(), iv.size());
size_t len;
if (EVP_AEAD_CTX_seal(
ctx_.get(), ct.data() + iv.size(), &len, ct.size() - iv.size(),
reinterpret_cast<const uint8_t*>(iv.data()), iv.size(),
reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size(),
reinterpret_cast<const uint8_t*>(additional_data.data()),
additional_data.size()) != 1) {
return util::Status(util::error::INTERNAL, "Encryption failed");
}
written += len;
ret = EVP_EncryptFinal_ex(ctx.get(), &ct[written], &len);
written += len;
if (ret != 1) {
return util::Status(util::error::INTERNAL, "EVP_EncryptFinal_ex failed");
}
ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, TAG_SIZE_IN_BYTES,
&ct[written]);
if (ret != 1) {
return util::Status(util::error::INTERNAL, "Could not compute tag");
}
written += TAG_SIZE_IN_BYTES;
if (written != ciphertext_size) {
return util::Status(util::error::INTERNAL, "Incorrect ciphertext size");
}
return std::string(reinterpret_cast<const char*>(&ct[0]), written);
return std::string(reinterpret_cast<const char*>(ct.data()), iv.size() + len);
}

util::StatusOr<std::string> AesGcmBoringSsl::Decrypt(
absl::string_view ciphertext,
absl::string_view additional_data) const {
// BoringSSL expects a non-null pointer for additional_data,
// regardless of whether the size is 0.
if (additional_data.size() == 0 && additional_data.data() == nullptr) {
additional_data = absl::string_view("");
}

absl::string_view ciphertext, absl::string_view additional_data) const {
if (ciphertext.size() < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) {
return util::Status(util::error::INTERNAL, "Ciphertext too short");
}

bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
if (ctx.get() == nullptr) {
return util::Status(util::error::INTERNAL,
"could not initialize EVP_CIPHER_CTX");
}
// Set the cipher.
int ret = EVP_DecryptInit_ex(ctx.get(), cipher_, nullptr, nullptr, nullptr);
if (ret != 1) {
return util::Status(util::error::INTERNAL, "EVP_DecryptInit_ex failed");
}
// Set IV and tag length.
ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, IV_SIZE_IN_BYTES,
nullptr);
if (ret != 1) {
return util::Status(util::error::INTERNAL, "IV length not supported");
}

// Initialise key and IV
ret = EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr,
reinterpret_cast<const uint8_t*>(key_.data()),
reinterpret_cast<const uint8_t*>(ciphertext.data()));
if (ret != 1) {
return util::Status(util::error::INTERNAL, "Could not initialize key");
}

// EVP_DecryptUpdate expects a non-null pointer for additional_data,
// regardless of whether the size is 0.
if (additional_data.data() == nullptr) {
additional_data = absl::string_view("");
}
int len;
ret = EVP_DecryptUpdate(
ctx.get(), nullptr, &len,
reinterpret_cast<const uint8_t*>(additional_data.data()),
additional_data.size());
if (ret != 1) {
return util::Status(util::error::INTERNAL, "AAD is not supported");
}
size_t plaintext_size =
ciphertext.size() - IV_SIZE_IN_BYTES - TAG_SIZE_IN_BYTES;
// Allocates 1 byte more than necessary because we may potentially access
// &pt[plaintext_size] causing vector range check error.
std::vector<uint8_t> pt(plaintext_size + 1);
size_t read = IV_SIZE_IN_BYTES;
size_t written = 0;
ret = EVP_DecryptUpdate(
ctx.get(), &pt[written], &len,
reinterpret_cast<const uint8_t*>(&ciphertext.data()[read]),
plaintext_size);
if (ret != 1) {
return util::Status(util::error::INTERNAL, "Decryption failed");
}
written += len;

// Copy the tag since EVP_CIPHER_CTX_ctrl does not accept const pointers.
uint8_t tag[TAG_SIZE_IN_BYTES];
memcpy(tag, &ciphertext.data()[ciphertext.size() - TAG_SIZE_IN_BYTES],
TAG_SIZE_IN_BYTES);
ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, TAG_SIZE_IN_BYTES,
tag);
if (ret != 1) {
return util::Status(util::error::INTERNAL, "Could not set tag");
}

ret = EVP_DecryptFinal_ex(ctx.get(), &pt[written], &len);
written += len;
if (ret != 1) {
std::vector<uint8_t> pt(ciphertext.size() - IV_SIZE_IN_BYTES -
TAG_SIZE_IN_BYTES);
size_t len;
if (EVP_AEAD_CTX_open(
ctx_.get(), pt.data(), &len, pt.size(),
// The nonce is the first |IV_SIZE_IN_BYTES| bytes of |ciphertext|.
reinterpret_cast<const uint8_t*>(ciphertext.data()), IV_SIZE_IN_BYTES,
// The input is the remainder.
reinterpret_cast<const uint8_t*>(ciphertext.data()) +
IV_SIZE_IN_BYTES,
ciphertext.size() - IV_SIZE_IN_BYTES,
reinterpret_cast<const uint8_t*>(additional_data.data()),
additional_data.size()) != 1) {
return util::Status(util::error::INTERNAL, "Authentication failed");
}
if (written != plaintext_size) {
return util::Status(util::error::INTERNAL, "Incorrect plaintext size");
}
return std::string(reinterpret_cast<const char*>(&pt[0]), written);
return std::string(reinterpret_cast<const char*>(pt.data()), len);
}

} // namespace subtle
Expand Down
11 changes: 3 additions & 8 deletions cc/subtle/aes_gcm_boringssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#include "tink/aead.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "openssl/evp.h"
#include "openssl/aead.h"

namespace crypto {
namespace tink {
Expand All @@ -49,14 +49,9 @@ class AesGcmBoringSsl : public Aead {
static const int TAG_SIZE_IN_BYTES = 16;

AesGcmBoringSsl() {}
AesGcmBoringSsl(absl::string_view key_value,
const EVP_CIPHER *cipher);
crypto::tink::util::Status Init(absl::string_view key_value);

const std::string key_;
// cipher_ is a singleton owned by BoringSsl.
// Preferable would be to use the AEAD interface, but unfortunately this
// interface does not support 192-bit keys.
const EVP_CIPHER *cipher_;
bssl::ScopedEVP_AEAD_CTX ctx_;
};

} // namespace subtle
Expand Down

0 comments on commit 48d066c

Please sign in to comment.