blob: f2cbd167e0fbeb673e1a3ad8e816fccc06d7e9d6 [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
#include "tink/subtle/aes_ctr_hmac_streaming.h"
#include <limits>
#include <string>
#include <vector>
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "openssl/base.h"
#include "openssl/cipher.h"
#include "openssl/err.h"
#include "openssl/evp.h"
#include "tink/subtle/common_enums.h"
#include "tink/subtle/hkdf.h"
#include "tink/subtle/hmac_boringssl.h"
#include "tink/subtle/random.h"
#include "tink/subtle/stream_segment_decrypter.h"
#include "tink/subtle/stream_segment_encrypter.h"
#include "tink/subtle/subtle_util.h"
#include "tink/subtle/subtle_util_boringssl.h"
#include "tink/util/errors.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
namespace crypto {
namespace tink {
namespace subtle {
static std::string NonceForSegment(absl::string_view nonce_prefix,
int64_t segment_number, bool is_last_segment) {
return absl::StrCat(nonce_prefix, BigEndian32(segment_number),
is_last_segment ? std::string(1, '\x01') : std::string(1, '\x00'),
std::string(4, '\x00'));
}
static util::Status DeriveKeys(absl::string_view ikm, HashType hkdf_algo,
absl::string_view salt,
absl::string_view associated_data, int key_size,
std::string* key_value, std::string* hmac_key_value) {
int derived_key_material_size =
key_size + AesCtrHmacStreaming::kHmacKeySizeInBytes;
auto hkdf_result = Hkdf::ComputeHkdf(hkdf_algo, ikm, salt, associated_data,
derived_key_material_size);
if (!hkdf_result.ok()) return hkdf_result.status();
std::string key_material = std::move(hkdf_result.ValueOrDie());
*key_value = key_material.substr(0, key_size);
*hmac_key_value =
key_material.substr(key_size, AesCtrHmacStreaming::kHmacKeySizeInBytes);
return util::OkStatus();
}
static util::Status Validate(const AesCtrHmacStreaming::Params& params) {
if (params.ikm.size() < std::max(16, params.key_size)) {
return util::Status(util::error::INVALID_ARGUMENT,
"input key material too small");
}
if (!(params.hkdf_algo == SHA1 || params.hkdf_algo == SHA256 ||
params.hkdf_algo == SHA512)) {
return util::Status(util::error::INVALID_ARGUMENT, "unsupported hkdf_algo");
}
if (params.key_size != 16 && params.key_size != 32) {
return util::Status(util::error::INVALID_ARGUMENT,
"key_size must be 16 or 32");
}
int header_size =
1 + params.key_size + AesCtrHmacStreaming::kNoncePrefixSizeInBytes;
if (params.ciphertext_segment_size <=
params.ciphertext_offset + header_size + params.tag_size) {
return util::Status(util::error::INVALID_ARGUMENT,
"ciphertext_segment_size too small");
}
if (params.ciphertext_offset < 0) {
return util::Status(util::error::INVALID_ARGUMENT,
"ciphertext_offset must be non-negative");
}
if (params.tag_size < 10) {
return util::Status(util::error::INVALID_ARGUMENT, "tag_size too small");
}
if (!(params.tag_algo == SHA1 || params.tag_algo == SHA256 ||
params.tag_algo == SHA512)) {
return util::Status(util::error::INVALID_ARGUMENT, "unsupported tag_algo");
}
if ((params.tag_algo == SHA1 && params.tag_size > 20) ||
(params.tag_algo == SHA256 && params.tag_size > 32) ||
(params.tag_algo == SHA512 && params.tag_size > 64)) {
return util::Status(util::error::INVALID_ARGUMENT, "tag_size too big");
}
return util::OkStatus();
}
// AesCtrHmacStreaming
// static
util::StatusOr<std::unique_ptr<AesCtrHmacStreaming>> AesCtrHmacStreaming::New(
const Params& params) {
auto status = Validate(params);
if (!status.ok()) return status;
return {absl::WrapUnique(new AesCtrHmacStreaming(params))};
}
// static
util::StatusOr<std::unique_ptr<StreamSegmentEncrypter>>
AesCtrHmacStreaming::NewSegmentEncrypter(
absl::string_view associated_data) const {
return AesCtrHmacStreamSegmentEncrypter::New(params_, associated_data);
}
// static
util::StatusOr<std::unique_ptr<StreamSegmentDecrypter>>
AesCtrHmacStreaming::NewSegmentDecrypter(
absl::string_view associated_data) const {
return AesCtrHmacStreamSegmentDecrypter::New(params_, associated_data);
}
// AesCtrHmacStreamSegmentEncrypter
static std::string MakeHeader(absl::string_view salt,
absl::string_view nonce_prefix) {
uint8_t header_size =
static_cast<uint8_t>(1 + salt.size() + nonce_prefix.size());
return absl::StrCat(std::string(1, header_size), salt, nonce_prefix);
}
// static
util::StatusOr<std::unique_ptr<StreamSegmentEncrypter>>
AesCtrHmacStreamSegmentEncrypter::New(const AesCtrHmacStreaming::Params& params,
absl::string_view associated_data) {
auto status = Validate(params);
if (!status.ok()) return status;
std::string salt = Random::GetRandomBytes(params.key_size);
std::string nonce_prefix =
Random::GetRandomBytes(AesCtrHmacStreaming::kNoncePrefixSizeInBytes);
std::string header = MakeHeader(salt, nonce_prefix);
std::string key_value, hmac_key_value;
status = DeriveKeys(params.ikm, params.hkdf_algo, salt, associated_data,
params.key_size, &key_value, &hmac_key_value);
if (!status.ok()) return status;
auto cipher = SubtleUtilBoringSSL::GetAesCtrCipherForKeySize(params.key_size);
if (cipher == nullptr) {
return util::Status(util::error::INTERNAL, "invalid key size");
}
auto hmac_result =
HmacBoringSsl::New(params.tag_algo, params.tag_size, hmac_key_value);
if (!hmac_result.ok()) return hmac_result.status();
auto mac = std::move(hmac_result.ValueOrDie());
return {absl::WrapUnique(new AesCtrHmacStreamSegmentEncrypter(
key_value, header, nonce_prefix, params.ciphertext_segment_size,
params.ciphertext_offset, params.tag_size, cipher, std::move(mac)))};
}
util::Status AesCtrHmacStreamSegmentEncrypter::EncryptSegment(
const std::vector<uint8_t>& plaintext, bool is_last_segment,
std::vector<uint8_t>* ciphertext_buffer) {
if (plaintext.size() > get_plaintext_segment_size()) {
return util::Status(util::error::INVALID_ARGUMENT, "plaintext too long");
}
if (ciphertext_buffer == nullptr) {
return util::Status(util::error::INVALID_ARGUMENT,
"ciphertext_buffer must be non-null");
}
if (get_segment_number() > std::numeric_limits<uint32_t>::max() ||
(get_segment_number() == std::numeric_limits<uint32_t>::max() &&
!is_last_segment)) {
return util::Status(util::error::INVALID_ARGUMENT, "too many segments");
}
int ct_size = plaintext.size() + tag_size_;
ciphertext_buffer->resize(ct_size);
std::string nonce =
NonceForSegment(nonce_prefix_, segment_number_, is_last_segment);
// Encrypt.
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");
}
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(util::error::INTERNAL, "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(util::error::INTERNAL, "encryption failed");
}
if (out_len != plaintext.size()) {
return util::Status(util::error::INTERNAL, "incorrect ciphertext size");
}
// Add MAC tag.
absl::string_view ciphertext_string(
reinterpret_cast<const char*>(ciphertext_buffer->data()),
plaintext.size());
auto tag_result = mac_->ComputeMac(absl::StrCat(nonce, ciphertext_string));
if (!tag_result.ok()) return tag_result.status();
std::string tag = tag_result.ValueOrDie();
memcpy(ciphertext_buffer->data() + plaintext.size(),
reinterpret_cast<const uint8_t*>(tag.data()), tag_size_);
IncSegmentNumber();
return util::OkStatus();
}
// AesCtrHmacStreamSegmentDecrypter
// static
util::StatusOr<std::unique_ptr<StreamSegmentDecrypter>>
AesCtrHmacStreamSegmentDecrypter::New(const AesCtrHmacStreaming::Params& params,
absl::string_view associated_data) {
auto status = Validate(params);
if (!status.ok()) return status;
return {absl::WrapUnique(new AesCtrHmacStreamSegmentDecrypter(
params.ikm, params.hkdf_algo, params.key_size, associated_data,
params.ciphertext_segment_size, params.ciphertext_offset, params.tag_algo,
params.tag_size))};
}
util::Status AesCtrHmacStreamSegmentDecrypter::Init(
const std::vector<uint8_t>& header) {
if (is_initialized_) {
return util::Status(util::error::FAILED_PRECONDITION,
"decrypter alreday initialized");
}
if (header.size() != get_header_size()) {
return util::Status(util::error::INVALID_ARGUMENT,
absl::StrCat("wrong header size, expected ",
get_header_size(), " bytes"));
}
if (header[0] != header.size()) {
return util::Status(util::error::INVALID_ARGUMENT, "corrupted header");
}
// Extract salt and nonce prefix.
std::string salt(reinterpret_cast<const char*>(header.data() + 1), key_size_);
nonce_prefix_ =
std::string(reinterpret_cast<const char*>(header.data() + 1 + key_size_),
AesCtrHmacStreaming::kNoncePrefixSizeInBytes);
std::string hmac_key_value;
auto status = DeriveKeys(ikm_, hkdf_algo_, salt, associated_data_, key_size_,
&key_value_, &hmac_key_value);
if (!status.ok()) return status;
cipher_ = SubtleUtilBoringSSL::GetAesCtrCipherForKeySize(key_size_);
if (cipher_ == nullptr) {
return util::Status(util::error::INTERNAL, "invalid key size");
}
auto hmac_result = HmacBoringSsl::New(tag_algo_, tag_size_, hmac_key_value);
if (!hmac_result.ok()) return hmac_result.status();
mac_ = std::move(hmac_result.ValueOrDie());
is_initialized_ = true;
return util::OkStatus();
}
util::Status AesCtrHmacStreamSegmentDecrypter::DecryptSegment(
const std::vector<uint8_t>& ciphertext, int64_t segment_number,
bool is_last_segment, std::vector<uint8_t>* plaintext_buffer) {
if (!is_initialized_) {
return util::Status(util::error::FAILED_PRECONDITION,
"decrypter not initialized");
}
if (ciphertext.size() > get_ciphertext_segment_size()) {
return util::Status(util::error::INVALID_ARGUMENT, "ciphertext too long");
}
if (ciphertext.size() < tag_size_) {
return util::Status(util::error::INVALID_ARGUMENT, "ciphertext too short");
}
if (plaintext_buffer == nullptr) {
return util::Status(util::error::INVALID_ARGUMENT,
"plaintext_buffer must be non-null");
}
if (segment_number > std::numeric_limits<uint32_t>::max() ||
(segment_number == std::numeric_limits<uint32_t>::max() &&
!is_last_segment)) {
return util::Status(util::error::INVALID_ARGUMENT, "too many segments");
}
int pt_size = ciphertext.size() - tag_size_;
plaintext_buffer->resize(pt_size);
std::string nonce =
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.
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");
}
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(util::error::INTERNAL, "could not initialize ctx");
}
int out_len;
if (EVP_DecryptUpdate(ctx.get(), plaintext_buffer->data(), &out_len,
ciphertext.data(), pt_size) != 1) {
return util::Status(util::error::INTERNAL, "decryption failed");
}
if (out_len != pt_size) {
return util::Status(util::error::INTERNAL, "incorrect plaintext size");
}
return util::OkStatus();
}
} // namespace subtle
} // namespace tink
} // namespace crypto