// 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/streamingaead/streaming_aead_wrapper.h" #include #include #include #include #include #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "tink/input_stream.h" #include "tink/insecure_secret_key_access.h" #include "tink/internal/test_random_access_stream.h" #include "tink/output_stream.h" #include "tink/primitive_set.h" #include "tink/proto_keyset_format.h" #include "tink/random_access_stream.h" #include "tink/streaming_aead.h" #include "tink/streamingaead/aes_gcm_hkdf_streaming_key_manager.h" #include "tink/streamingaead/streaming_aead_config.h" #include "tink/subtle/random.h" #include "tink/subtle/streaming_aead_test_util.h" #include "tink/subtle/test_util.h" #include "tink/util/buffer.h" #include "tink/util/istream_input_stream.h" #include "tink/util/ostream_output_stream.h" #include "tink/util/status.h" #include "tink/util/test_matchers.h" #include "tink/util/test_util.h" #include "proto/aes_gcm_hkdf_streaming.pb.h" #include "proto/common.pb.h" #include "proto/tink.pb.h" namespace crypto { namespace tink { namespace { using ::crypto::tink::test::DummyStreamingAead; using ::crypto::tink::test::IsOk; using ::crypto::tink::test::StatusIs; using ::google::crypto::tink::KeysetInfo; using ::google::crypto::tink::KeyStatusType; using ::google::crypto::tink::OutputPrefixType; using ::crypto::tink::subtle::test::ReadFromStream; using ::crypto::tink::subtle::test::WriteToStream; using ::testing::HasSubstr; // A container for specification of instances of DummyStreamingAead // to be created for testing. struct StreamingAeadSpec { uint32_t key_id; std::string saead_name; OutputPrefixType output_prefix_type; }; // Generates a PrimitiveSet with DummyStreamingAead // instances according to the specification in 'spec'. // The last entry in 'spec' will be the primary primitive in the returned set. std::unique_ptr> GetTestStreamingAeadSet( const std::vector& spec) { auto saead_set = absl::make_unique>(); int i = 0; for (auto& s : spec) { KeysetInfo::KeyInfo key_info; key_info.set_output_prefix_type(s.output_prefix_type); key_info.set_key_id(s.key_id); key_info.set_status(KeyStatusType::ENABLED); std::unique_ptr saead = absl::make_unique(s.saead_name); auto entry_result = saead_set->AddPrimitive(std::move(saead), key_info); EXPECT_TRUE(entry_result.ok()); if (i + 1 == spec.size()) { EXPECT_THAT(saead_set->set_primary(entry_result.value()), IsOk()); } i++; } return saead_set; } TEST(StreamingAeadSetWrapperTest, WrapNullptr) { StreamingAeadWrapper wrapper; auto result = wrapper.Wrap(nullptr); EXPECT_FALSE(result.ok()); EXPECT_EQ(absl::StatusCode::kInternal, result.status().code()); EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL", std::string(result.status().message())); } TEST(StreamingAeadSetWrapperTest, WrapEmpty) { StreamingAeadWrapper wrapper; auto result = wrapper.Wrap(absl::make_unique>()); EXPECT_FALSE(result.ok()); EXPECT_EQ(absl::StatusCode::kInvalidArgument, result.status().code()); EXPECT_PRED_FORMAT2(testing::IsSubstring, "no primary", std::string(result.status().message())); } TEST(StreamingAeadSetWrapperTest, BasicEncryptionAndDecryption) { uint32_t key_id_0 = 1234543; uint32_t key_id_1 = 726329; uint32_t key_id_2 = 7213743; std::string saead_name_0 = "streaming_aead0"; std::string saead_name_1 = "streaming_aead1"; std::string saead_name_2 = "streaming_aead2"; auto saead_set = GetTestStreamingAeadSet( {{key_id_0, saead_name_0, OutputPrefixType::RAW}, {key_id_1, saead_name_1, OutputPrefixType::RAW}, {key_id_2, saead_name_2, OutputPrefixType::RAW}}); // Wrap saead_set and test the resulting StreamingAead. StreamingAeadWrapper wrapper; auto wrap_result = wrapper.Wrap(std::move(saead_set)); EXPECT_TRUE(wrap_result.ok()) << wrap_result.status(); auto saead = std::move(wrap_result.value()); for (int pt_size : {0, 1, 10, 100, 10000}) { std::string plaintext = subtle::Random::GetRandomBytes(pt_size); for (std::string aad : {"some_aad", "", "some other aad"}) { SCOPED_TRACE(absl::StrCat("pt_size = ", pt_size, ", aad = '", aad, "'")); // Prepare ciphertext destination stream. auto ct_stream = absl::make_unique(); // A reference to the ciphertext buffer, for later validation. auto ct_buf = ct_stream->rdbuf(); std::unique_ptr ct_destination( absl::make_unique(std::move(ct_stream))); // Encrypt the plaintext. auto enc_stream_result = saead->NewEncryptingStream(std::move(ct_destination), aad); EXPECT_THAT(enc_stream_result, IsOk()); auto enc_stream = std::move(enc_stream_result.value()); auto status = WriteToStream(enc_stream.get(), plaintext); EXPECT_THAT(status, IsOk()); EXPECT_EQ(absl::StrCat(saead_name_2, aad, plaintext), ct_buf->str()); // Prepare ciphertext source stream. auto ct_source_stream = absl::make_unique(ct_buf->str()); std::unique_ptr ct_source( absl::make_unique( std::move(ct_source_stream))); // Decrypt the ciphertext. auto dec_stream_result = saead->NewDecryptingStream(std::move(ct_source), aad); EXPECT_THAT(dec_stream_result, IsOk()); std::string decrypted; status = ReadFromStream(dec_stream_result.value().get(), &decrypted); EXPECT_THAT(status, IsOk()); EXPECT_EQ(plaintext, decrypted); } } } TEST(StreamingAeadSetWrapperTest, DecryptionWithRandomAccessStream) { uint32_t key_id_0 = 1234543; uint32_t key_id_1 = 726329; uint32_t key_id_2 = 7213743; std::string saead_name_0 = "streaming_aead0"; std::string saead_name_1 = "streaming_aead1"; std::string saead_name_2 = "streaming_aead2"; auto saead_set = GetTestStreamingAeadSet( {{key_id_0, saead_name_0, OutputPrefixType::RAW}, {key_id_1, saead_name_1, OutputPrefixType::RAW}, {key_id_2, saead_name_2, OutputPrefixType::RAW}}); // Wrap saead_set and test the resulting StreamingAead. StreamingAeadWrapper wrapper; auto wrap_result = wrapper.Wrap(std::move(saead_set)); EXPECT_TRUE(wrap_result.ok()) << wrap_result.status(); auto saead = std::move(wrap_result.value()); for (int pt_size : {0, 1, 10, 100, 10000}) { std::string plaintext = subtle::Random::GetRandomBytes(pt_size); for (std::string aad : {"some_aad", "", "some other aad"}) { SCOPED_TRACE(absl::StrCat("pt_size = ", pt_size, ", aad = '", aad, "'")); // Prepare ciphertext destination stream. auto ct_stream = absl::make_unique(); // A reference to the ciphertext buffer, for later validation. auto ct_buf = ct_stream->rdbuf(); std::unique_ptr ct_destination( absl::make_unique(std::move(ct_stream))); // Encrypt the plaintext. auto enc_stream_result = saead->NewEncryptingStream(std::move(ct_destination), aad); EXPECT_THAT(enc_stream_result, IsOk()); auto enc_stream = std::move(enc_stream_result.value()); auto status = WriteToStream(enc_stream.get(), plaintext); EXPECT_THAT(status, IsOk()); EXPECT_EQ(absl::StrCat(saead_name_2, aad, plaintext), ct_buf->str()); // Decrypt the ciphertext. auto ct_source = std::make_unique(ct_buf->str()); auto dec_stream_result = saead->NewDecryptingRandomAccessStream(std::move(ct_source), aad); EXPECT_THAT(dec_stream_result, IsOk()); std::string decrypted; status = internal::ReadAllFromRandomAccessStream( dec_stream_result.value().get(), decrypted); EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange, HasSubstr("EOF"))); EXPECT_EQ(plaintext, decrypted); } } } TEST(StreamingAeadSetWrapperTest, DecryptionAfterWrapperIsDestroyed) { uint32_t key_id_0 = 1234543; uint32_t key_id_1 = 726329; uint32_t key_id_2 = 7213743; std::string saead_name_0 = "streaming_aead0"; std::string saead_name_1 = "streaming_aead1"; std::string saead_name_2 = "streaming_aead2"; auto saead_set = GetTestStreamingAeadSet( {{key_id_0, saead_name_0, OutputPrefixType::RAW}, {key_id_1, saead_name_1, OutputPrefixType::RAW}, {key_id_2, saead_name_2, OutputPrefixType::RAW}}); int pt_size = 100; std::string plaintext = subtle::Random::GetRandomBytes(pt_size); std::string aad = "some_aad"; std::unique_ptr dec_stream; { // Wrap saead_set and test the resulting StreamingAead. StreamingAeadWrapper wrapper; auto wrap_result = wrapper.Wrap(std::move(saead_set)); EXPECT_TRUE(wrap_result.ok()) << wrap_result.status(); auto saead = std::move(wrap_result.value()); // Prepare ciphertext destination stream. auto ct_stream = absl::make_unique(); // A reference to the ciphertext buffer, for later validation. auto ct_buf = ct_stream->rdbuf(); std::unique_ptr ct_destination( absl::make_unique(std::move(ct_stream))); // Encrypt the plaintext. auto enc_stream_result = saead->NewEncryptingStream(std::move(ct_destination), aad); EXPECT_THAT(enc_stream_result, IsOk()); auto enc_stream = std::move(enc_stream_result.value()); auto status = WriteToStream(enc_stream.get(), plaintext); EXPECT_THAT(status, IsOk()); EXPECT_EQ(absl::StrCat(saead_name_2, aad, plaintext), ct_buf->str()); // Prepare ciphertext source stream. auto ct_source_stream = absl::make_unique(ct_buf->str()); std::unique_ptr ct_source( absl::make_unique( std::move(ct_source_stream))); // Decrypt the ciphertext. auto dec_stream_result = saead->NewDecryptingStream(std::move(ct_source), aad); EXPECT_THAT(dec_stream_result, IsOk()); dec_stream = std::move(dec_stream_result.value()); } // Now wrapper and saead are out of scope, // but decrypting stream should still work. std::string decrypted; auto status = ReadFromStream(dec_stream.get(), &decrypted); EXPECT_THAT(status, IsOk()); EXPECT_EQ(plaintext, decrypted); } TEST(StreamingAeadSetWrapperTest, EncryptWithTink) { ASSERT_THAT(StreamingAeadConfig::Register(), IsOk()); google::crypto::tink::AesGcmHkdfStreamingKey key; key.set_key_value("0123456789012345"); google::crypto::tink::AesGcmHkdfStreamingParams& params = *key.mutable_params(); params.set_hkdf_hash_type(google::crypto::tink::HashType::SHA1); params.set_derived_key_size(16); params.set_ciphertext_segment_size(1024); std::string serialized_key_1 = key.SerializeAsString(); key.set_key_value("0123456789abcdef"); std::string serialized_key_2 = key.SerializeAsString(); google::crypto::tink::Keyset keyset; { google::crypto::tink::Keyset::Key& keyset_key = *keyset.add_key(); google::crypto::tink::KeyData& key_data = *keyset_key.mutable_key_data(); key_data.set_type_url(AesGcmHkdfStreamingKeyManager().get_key_type()); key_data.set_value(serialized_key_1); key_data.set_key_material_type(google::crypto::tink::KeyData::SYMMETRIC); keyset_key.set_key_id(1); keyset_key.set_output_prefix_type( google::crypto::tink::OutputPrefixType::TINK); keyset_key.set_status(google::crypto::tink::KeyStatusType::ENABLED); keyset.set_primary_key_id(1); } { google::crypto::tink::Keyset::Key& keyset_key = *keyset.add_key(); google::crypto::tink::KeyData& key_data = *keyset_key.mutable_key_data(); key_data.set_type_url(AesGcmHkdfStreamingKeyManager().get_key_type()); key_data.set_value(serialized_key_2); key_data.set_key_material_type(google::crypto::tink::KeyData::SYMMETRIC); keyset_key.set_key_id(2); keyset_key.set_output_prefix_type( google::crypto::tink::OutputPrefixType::RAW); keyset_key.set_status(google::crypto::tink::KeyStatusType::ENABLED); } crypto::tink::util::StatusOr handle = ParseKeysetFromProtoKeysetFormat(keyset.SerializeAsString(), InsecureSecretKeyAccess::Get()); ASSERT_THAT(handle.status(), IsOk()); crypto::tink::util::StatusOr> streaming_aead = handle->GetPrimitive(); ASSERT_THAT(streaming_aead.status(), IsOk()); EXPECT_THAT(EncryptThenDecrypt(streaming_aead.value().get(), streaming_aead.value().get(), subtle::Random::GetRandomBytes(10000), "some associated data", 0), IsOk()); } } // namespace } // namespace tink } // namespace crypto