blob: 0ea741bfb4e00171050e661ae70b44a5cf0f70c1 [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 <string>
#include <vector>
#include "absl/strings/str_cat.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_encrypter.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "tink/util/test_util.h"
#include "gtest/gtest.h"
namespace crypto {
namespace tink {
namespace subtle {
namespace {
util::StatusOr<std::unique_ptr<StreamSegmentEncrypter>>
GetEncrypter(absl::string_view ikm,
HashType hkdf_hash,
int derived_key_size,
int ciphertext_offset,
int ciphertext_segment_size,
absl::string_view associated_data) {
AesGcmHkdfStreamSegmentEncrypter::Params params;
params.salt = Random::GetRandomBytes(derived_key_size);
auto hkdf_result = Hkdf::ComputeHkdf(
hkdf_hash, ikm, params.salt, associated_data,
derived_key_size);
if (!hkdf_result.ok()) return hkdf_result.status();
params.key_value = hkdf_result.ValueOrDie();
params.ciphertext_offset = ciphertext_offset;
params.ciphertext_segment_size = ciphertext_segment_size;
return AesGcmHkdfStreamSegmentEncrypter::New(params);
}
TEST(AesGcmHkdfStreamSegmentDecrypterTest, testBasic) {
for (int ikm_size : {16, 32}) {
for (HashType hkdf_hash : {SHA1, SHA256, SHA512}) {
for (int derived_key_size = 16;
derived_key_size <= ikm_size;
derived_key_size += 16) {
for (int ciphertext_offset : {0, 5, 10}) {
for (int ct_segment_size : {80, 128, 200}) {
for (std::string associated_data : {"associated data", "42", ""}) {
SCOPED_TRACE(absl::StrCat(
"hkdf_hash = ", EnumToString(hkdf_hash),
", ikm_size = ", ikm_size,
", associated_data = '", associated_data, "'",
", derived_key_size = ", derived_key_size,
", ciphertext_offset = ", ciphertext_offset,
", ciphertext_segment_size = ", ct_segment_size));
// Construct a decrypter.
AesGcmHkdfStreamSegmentDecrypter::Params params;
params.ikm = Random::GetRandomBytes(ikm_size);
params.hkdf_hash = hkdf_hash;
params.derived_key_size = derived_key_size;
params.ciphertext_offset = ciphertext_offset;
params.ciphertext_segment_size = ct_segment_size;
params.associated_data = associated_data;
auto result = AesGcmHkdfStreamSegmentDecrypter::New(params);
EXPECT_TRUE(result.ok()) << result.status();
auto dec = std::move(result.ValueOrDie());
// Try to use the decrypter.
std::vector<uint8_t> pt;
auto status = dec->DecryptSegment(pt, 42, false, nullptr);
EXPECT_FALSE(status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, status.error_code());
EXPECT_PRED_FORMAT2(testing::IsSubstring, "not initialized",
status.error_message());
// Get an encrypter and initialize the decrypter.
auto enc = std::move(
GetEncrypter(params.ikm, hkdf_hash, derived_key_size,
ciphertext_offset, ct_segment_size,
associated_data).ValueOrDie());
status = dec->Init(enc->get_header());
EXPECT_TRUE(status.ok()) << status;
// Use the constructed decrypter.
int header_size =
derived_key_size + /* nonce_prefix_size = */ 7 + 1;
EXPECT_EQ(header_size, dec->get_header_size());
EXPECT_EQ(enc->get_header().size(), dec->get_header_size());
EXPECT_EQ(ct_segment_size, dec->get_ciphertext_segment_size());
EXPECT_EQ(ct_segment_size - /* tag_size = */ 16,
dec->get_plaintext_segment_size());
EXPECT_EQ(ciphertext_offset, dec->get_ciphertext_offset());
int segment_number = 0;
for (int pt_size : {1, 10, dec->get_plaintext_segment_size()}) {
for (bool is_last_segment : {false, true}) {
SCOPED_TRACE(absl::StrCat(
"plaintext_size = ", pt_size,
", is_last_segment = ", is_last_segment));
std::vector<uint8_t> pt(pt_size, 'p');
std::vector<uint8_t> ct;
std::vector<uint8_t> decrypted;
auto status = enc->EncryptSegment(pt, is_last_segment, &ct);
EXPECT_TRUE(status.ok()) << status;
status = dec->DecryptSegment(ct, segment_number,
is_last_segment, &decrypted);
EXPECT_TRUE(status.ok()) << status;
EXPECT_EQ(pt, decrypted);
segment_number++;
EXPECT_EQ(segment_number, enc->get_segment_number());
}
}
// Try decryption with wrong params.
std::vector<uint8_t> ct(
dec->get_ciphertext_segment_size() + 1, 'c');
status = dec->DecryptSegment(ct, 42, true, nullptr);
EXPECT_FALSE(status.ok());
EXPECT_PRED_FORMAT2(testing::IsSubstring, "ciphertext too long",
status.error_message());
ct.resize(dec->get_plaintext_segment_size());
status = dec->DecryptSegment(ct, 42, true, nullptr);
EXPECT_FALSE(status.ok());
EXPECT_PRED_FORMAT2(testing::IsSubstring, "must be non-null",
status.error_message());
}
}
}
}
}
}
}
TEST(AesGcmHkdfStreamSegmentDecrypterTest, testWrongDerivedKeySize) {
for (int derived_key_size : {12, 24, 64}) {
for (HashType hkdf_hash : {SHA1, SHA256, SHA512}) {
for (int ct_segment_size : {128, 200}) {
SCOPED_TRACE(absl::StrCat(
"derived_key_size = ", derived_key_size,
", hkdf_hash = ", EnumToString(hkdf_hash),
", ciphertext_segment_size = ", ct_segment_size));
AesGcmHkdfStreamSegmentDecrypter::Params params;
params.ikm = Random::GetRandomBytes(derived_key_size);
params.hkdf_hash = hkdf_hash;
params.derived_key_size = derived_key_size;
params.ciphertext_offset = 0;
params.ciphertext_segment_size = ct_segment_size;
params.associated_data = "associated data";
auto result = AesGcmHkdfStreamSegmentDecrypter::New(params);
EXPECT_FALSE(result.ok());
EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
EXPECT_PRED_FORMAT2(testing::IsSubstring, "must be 16 or 32",
result.status().error_message());
}
}
}
}
TEST(AesGcmHkdfStreamSegmentDecrypterTest, testWrongIkmSize) {
for (int derived_key_size : {16, 32}) {
for (HashType hkdf_hash : {SHA1, SHA256, SHA512}) {
for (int ikm_size_delta : {-8, -4, -2, -1}) {
SCOPED_TRACE(absl::StrCat(
"derived_key_size = ", derived_key_size,
", hkdf_hash = ", EnumToString(hkdf_hash),
", ikm_size_delta = ", ikm_size_delta));
AesGcmHkdfStreamSegmentDecrypter::Params params;
params.ikm = Random::GetRandomBytes(derived_key_size + ikm_size_delta);
params.hkdf_hash = hkdf_hash;
params.derived_key_size = derived_key_size;
params.ciphertext_offset = 0;
params.ciphertext_segment_size = 128;
params.associated_data = "associated data";
auto result = AesGcmHkdfStreamSegmentDecrypter::New(params);
EXPECT_FALSE(result.ok());
EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
EXPECT_PRED_FORMAT2(testing::IsSubstring, "ikm too small",
result.status().error_message());
}
}
}
}
TEST(AesGcmHkdfStreamSegmentDecrypterTest, testWrongCiphertextOffset) {
for (int derived_key_size : {16, 32}) {
for (HashType hkdf_hash : {SHA1, SHA256, SHA512}) {
for (int ciphertext_offset : {-16, -10, -3, -1}) {
SCOPED_TRACE(absl::StrCat(
"derived_key_size = ", derived_key_size,
", hkdf_hash = ", EnumToString(hkdf_hash),
", ciphertext_offset = ", ciphertext_offset));
AesGcmHkdfStreamSegmentDecrypter::Params params;
params.ikm = Random::GetRandomBytes(derived_key_size);
params.hkdf_hash = hkdf_hash;
params.derived_key_size = derived_key_size;
params.ciphertext_offset = ciphertext_offset;
params.ciphertext_segment_size = 128;
params.associated_data = "associated data";
auto result = AesGcmHkdfStreamSegmentDecrypter::New(params);
EXPECT_FALSE(result.ok());
EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
EXPECT_PRED_FORMAT2(testing::IsSubstring, "must be non-negative",
result.status().error_message());
}
}
}
}
TEST(AesGcmHkdfStreamSegmentDecrypterTest, testWrongCiphertextSegmentSize) {
for (int derived_key_size : {16, 32}) {
for (HashType hkdf_hash : {SHA1, SHA256, SHA512}) {
for (int ciphertext_offset : {0, 1, 5, 10}) {
int min_ct_segment_size = derived_key_size + ciphertext_offset +
8 + // nonce_prefix_size + 1
16 + // tag_size
1;
for (int ct_segment_size : {min_ct_segment_size - 5,
min_ct_segment_size - 1, min_ct_segment_size,
min_ct_segment_size + 1, min_ct_segment_size + 10}) {
SCOPED_TRACE(absl::StrCat(
"derived_key_size = ", derived_key_size,
", ciphertext_offset = ", ciphertext_offset,
", ciphertext_segment_size = ", ct_segment_size));
AesGcmHkdfStreamSegmentDecrypter::Params params;
params.ikm = Random::GetRandomBytes(derived_key_size);
params.hkdf_hash = hkdf_hash;
params.derived_key_size = derived_key_size;
params.ciphertext_offset = ciphertext_offset;
params.ciphertext_segment_size = ct_segment_size;
auto result = AesGcmHkdfStreamSegmentDecrypter::New(params);
if (ct_segment_size < min_ct_segment_size) {
EXPECT_FALSE(result.ok());
EXPECT_EQ(util::error::INVALID_ARGUMENT,
result.status().error_code());
EXPECT_PRED_FORMAT2(testing::IsSubstring, "too small",
result.status().error_message());
} else {
EXPECT_TRUE(result.ok()) << result.status();
}
}
}
}
}
}
} // namespace
} // namespace subtle
} // namespace tink
} // namespace crypto