blob: 5ad9a2d475537f1ca48cc7ece7c9998a67ba9445 [file] [log] [blame]
// Copyright 2019 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_decrypter.h"
#include <limits>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tink/subtle/aes_gcm_hkdf_stream_segment_encrypter.h"
#include "tink/subtle/common_enums.h"
#include "tink/subtle/hkdf.h"
#include "tink/subtle/random.h"
#include "tink/subtle/stream_segment_decrypter.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
util::Status Validate(const AesGcmHkdfStreamSegmentDecrypter::Params& params) {
if (!(params.hkdf_hash == SHA1 || params.hkdf_hash == SHA256 ||
params.hkdf_hash == SHA512)) {
return util::Status(util::error::INVALID_ARGUMENT, "unsupported hkdf_hash");
}
if (params.derived_key_size != 16 && params.derived_key_size != 32) {
return util::Status(util::error::INVALID_ARGUMENT,
"derived_key_size must be 16 or 32");
}
if (params.ikm.size() < 16 || params.ikm.size() < params.derived_key_size) {
return util::Status(util::error::INVALID_ARGUMENT, "ikm too small");
}
if (params.ciphertext_offset < 0) {
return util::Status(util::error::INVALID_ARGUMENT,
"ciphertext_offset must be non-negative");
}
int header_size = 1 + params.derived_key_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();
}
// static
util::StatusOr<std::unique_ptr<StreamSegmentDecrypter>>
AesGcmHkdfStreamSegmentDecrypter::New(const Params& params) {
auto status = Validate(params);
if (!status.ok()) return status;
std::unique_ptr<AesGcmHkdfStreamSegmentDecrypter>
decrypter(new AesGcmHkdfStreamSegmentDecrypter());
decrypter->ikm_ = params.ikm;
decrypter->hkdf_hash_ = params.hkdf_hash;
int header_size = 1 + params.derived_key_size +
AesGcmHkdfStreamSegmentEncrypter::kNoncePrefixSizeInBytes;
decrypter->header_size_ = header_size;
decrypter->ciphertext_offset_ = params.ciphertext_offset;
decrypter->ciphertext_segment_size_ = params.ciphertext_segment_size;
decrypter->derived_key_size_ = params.derived_key_size;
decrypter->associated_data_ = params.associated_data;
decrypter->is_initialized_ = false;
return {std::move(decrypter)};
}
util::Status AesGcmHkdfStreamSegmentDecrypter::Init(
const std::vector<uint8_t>& header) {
if (is_initialized_) {
return util::Status(util::error::FAILED_PRECONDITION,
"decrypter already initialized");
}
if (header.size() != header_size_) {
return util::Status(util::error::INVALID_ARGUMENT,
absl::StrCat("wrong header size, expected ", header_size_, " bytes"));
}
if (header[0] != header_size_) {
return util::Status(util::error::INVALID_ARGUMENT, "corrupted header");
}
// Extract salt and nonce_prefix.
salt_.resize(derived_key_size_);
nonce_prefix_.resize(
AesGcmHkdfStreamSegmentEncrypter::kNoncePrefixSizeInBytes);
memcpy(salt_.data(), header.data() + 1, derived_key_size_);
memcpy(nonce_prefix_.data(), header.data() + 1 + derived_key_size_,
AesGcmHkdfStreamSegmentEncrypter::kNoncePrefixSizeInBytes);
// Derive symmetric key.
auto hkdf_result = Hkdf::ComputeHkdf(
hkdf_hash_, ikm_,
std::string(reinterpret_cast<const char *>(salt_.data()), derived_key_size_),
associated_data_, derived_key_size_);
if (!hkdf_result.ok()) return hkdf_result.status();
key_value_ = hkdf_result.ValueOrDie();
// Initialize ctx_.
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(),
AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes,
nullptr) != 1) {
return util::Status(util::error::INTERNAL,
"could not initialize EVP_AEAD_CTX");
}
is_initialized_ = true;
return util::OkStatus();
}
int AesGcmHkdfStreamSegmentDecrypter::get_plaintext_segment_size() const {
return ciphertext_segment_size_ -
AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes;
}
util::Status AesGcmHkdfStreamSegmentDecrypter::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() < AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes) {
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() - AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes;
plaintext_buffer->resize(pt_size);
// Construct IV.
std::vector<uint8_t> iv(AesGcmHkdfStreamSegmentEncrypter::kNonceSizeInBytes);
memcpy(iv.data(), nonce_prefix_.data(),
AesGcmHkdfStreamSegmentEncrypter::kNoncePrefixSizeInBytes);
BigEndianStore32(
iv.data() + AesGcmHkdfStreamSegmentEncrypter::kNoncePrefixSizeInBytes,
static_cast<uint32_t>(segment_number));
iv.back() = is_last_segment ? 1 : 0;
// Decrypt.
size_t out_len;
if (!EVP_AEAD_CTX_open(
ctx_.get(), plaintext_buffer->data(), &out_len,
plaintext_buffer->size(),
iv.data(), iv.size(),
ciphertext.data(), ciphertext.size(),
/* ad = */ nullptr, /* ad.length() = */ 0)) {
return util::Status(util::error::INTERNAL,
absl::StrCat("Decryption failed: ",
SubtleUtilBoringSSL::GetErrors()));
}
return util::OkStatus();
}
} // namespace subtle
} // namespace tink
} // namespace crypto