blob: 2c4d6a9d15a65a5aace45d5e90620169b5300fef [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// 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_gcm_hkdf_stream_segment_encrypter.h"
#include <limits>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tink/subtle/random.h"
#include "tink/subtle/stream_segment_encrypter.h"
#include "tink/subtle/subtle_util_boringssl.h"
#include "tink/util/errors.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "openssl/aead.h"
#include "openssl/err.h"
namespace crypto {
namespace tink {
namespace subtle {
namespace {
void BigEndianStore32(uint8_t dst[8], uint32_t val) {
dst[0] = (val >> 24) & 0xff;
dst[1] = (val >> 16) & 0xff;
dst[2] = (val >> 8) & 0xff;
dst[3] = val & 0xff;
}
} // namespace
const int AesGcmHkdfStreamSegmentEncrypter::kNonceSizeInBytes;
const int AesGcmHkdfStreamSegmentEncrypter::kNoncePrefixSizeInBytes;
const int AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes;
util::Status Validate(const AesGcmHkdfStreamSegmentEncrypter::Params& params) {
if (params.key_value.size() != 16 && params.key_value.size() != 32) {
return util::Status(util::error::INVALID_ARGUMENT,
"key_value must have 16 or 32 bytes");
}
if (params.key_value.size() != params.salt.size()) {
return util::Status(util::error::INVALID_ARGUMENT,
"salt must have same size as key_value");
}
if (params.ciphertext_offset < 0) {
return util::Status(util::error::INVALID_ARGUMENT,
"ciphertext_offset must be non-negative");
}
int header_size = 1 + params.salt.size() +
AesGcmHkdfStreamSegmentEncrypter::kNoncePrefixSizeInBytes;
if (params.ciphertext_segment_size <=
params.ciphertext_offset + header_size +
AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes) {
return util::Status(util::error::INVALID_ARGUMENT,
"ciphertext_segment_size too small");
}
return util::OkStatus();
}
util::Status AesGcmHkdfStreamSegmentEncrypter::InitCtx(
absl::string_view key_value) {
const EVP_AEAD* aead =
SubtleUtilBoringSSL::GetAesGcmAeadForKeySize(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(), kTagSizeInBytes, nullptr) != 1) {
return util::Status(util::error::INTERNAL,
"could not initialize EVP_AEAD_CTX");
}
return util::OkStatus();
}
int AesGcmHkdfStreamSegmentEncrypter::get_plaintext_segment_size() const {
return ciphertext_segment_size_ - kTagSizeInBytes;
}
// static
util::StatusOr<std::unique_ptr<StreamSegmentEncrypter>>
AesGcmHkdfStreamSegmentEncrypter::New(const Params& params) {
auto status = Validate(params);
if (!status.ok()) return status;
std::unique_ptr<AesGcmHkdfStreamSegmentEncrypter>
encrypter(new AesGcmHkdfStreamSegmentEncrypter());
status = encrypter->InitCtx(params.key_value);
if (!status.ok()) return status;
uint8_t header_size =
static_cast<uint8_t>(1 + params.salt.size() + kNoncePrefixSizeInBytes);
encrypter->ciphertext_offset_ = params.ciphertext_offset;
encrypter->ciphertext_segment_size_ = params.ciphertext_segment_size;
encrypter->nonce_prefix_ = Random::GetRandomBytes(kNoncePrefixSizeInBytes);
encrypter->header_.resize(header_size);
encrypter->header_[0] = header_size;
memcpy(encrypter->header_.data() + 1, params.salt.data(), params.salt.size());
memcpy(encrypter->header_.data() + 1 + params.salt.size(),
encrypter->nonce_prefix_.data(), encrypter->nonce_prefix_.size());
return {std::move(encrypter)};
}
util::Status AesGcmHkdfStreamSegmentEncrypter::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() + kTagSizeInBytes;
ciphertext_buffer->resize(ct_size);
// Construct IV.
std::vector<uint8_t> iv(kNonceSizeInBytes);
memcpy(iv.data(), nonce_prefix_.data(), kNoncePrefixSizeInBytes);
BigEndianStore32(iv.data() + kNoncePrefixSizeInBytes,
static_cast<uint32_t>(get_segment_number()));
iv.back() = is_last_segment ? 1 : 0;
size_t out_len;
if (!EVP_AEAD_CTX_seal(
ctx_.get(), ciphertext_buffer->data(), &out_len,
ciphertext_buffer->size(),
iv.data(), iv.size(),
plaintext.data(), plaintext.size(),
/* ad = */ nullptr, /* ad.length() = */ 0)) {
return util::Status(util::error::INTERNAL,
absl::StrCat("Encryption failed: ",
SubtleUtilBoringSSL::GetErrors()));
}
IncSegmentNumber();
return util::OkStatus();
}
} // namespace subtle
} // namespace tink
} // namespace crypto