| // 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/decrypting_random_access_stream.h" |
| |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #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/output_stream.h" |
| #include "tink/primitive_set.h" |
| #include "tink/random_access_stream.h" |
| #include "tink/streaming_aead.h" |
| #include "tink/subtle/random.h" |
| #include "tink/subtle/test_util.h" |
| #include "tink/util/file_random_access_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/tink.pb.h" |
| |
| namespace crypto { |
| namespace tink { |
| namespace streamingaead { |
| 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 subtle::test::WriteToStream; |
| using testing::HasSubstr; |
| |
| // Creates a RandomAccessStream with the specified contents. |
| std::unique_ptr<RandomAccessStream> GetRandomAccessStream( |
| absl::string_view contents) { |
| static int index = 1; |
| std::string filename = absl::StrCat("stream_data_file_", index, ".txt"); |
| index++; |
| int input_fd = test::GetTestFileDescriptor(filename, contents); |
| return {absl::make_unique<util::FileRandomAccessStream>(input_fd)}; |
| } |
| |
| // Creates an RandomAccessStream that contains ciphertext resulting |
| // from encryption of 'pt' with 'aad' as associated data, using 'saead'. |
| std::unique_ptr<RandomAccessStream> GetCiphertextSource( |
| StreamingAead* saead, absl::string_view pt, absl::string_view aad) { |
| // Prepare ciphertext destination stream. |
| auto ct_stream = absl::make_unique<std::stringstream>(); |
| // A reference to the ciphertext buffer. |
| auto ct_buf = ct_stream->rdbuf(); |
| std::unique_ptr<OutputStream> ct_destination( |
| absl::make_unique<util::OstreamOutputStream>(std::move(ct_stream))); |
| |
| // Compute the ciphertext. |
| auto enc_stream_result = |
| saead->NewEncryptingStream(std::move(ct_destination), aad); |
| EXPECT_THAT(enc_stream_result, IsOk()); |
| EXPECT_THAT(WriteToStream(enc_stream_result.value().get(), pt), IsOk()); |
| |
| // Return the ciphertext as RandomAccessStream. |
| return GetRandomAccessStream(ct_buf->str()); |
| } |
| |
| // Reads the entire 'ra_stream', until no more bytes can be read, |
| // and puts the read bytes into 'contents'. |
| // Returns the status of the last ra_stream->PRead()-operation. |
| util::Status ReadAll(RandomAccessStream* ra_stream, std::string* contents) { |
| int chunk_size = 42; |
| contents->clear(); |
| auto buffer = std::move(util::Buffer::New(chunk_size).value()); |
| int64_t position = 0; |
| auto status = ra_stream->PRead(position, chunk_size, buffer.get()); |
| while (status.ok()) { |
| contents->append(buffer->get_mem_block(), buffer->size()); |
| position = contents->size(); |
| status = ra_stream->PRead(position, chunk_size, buffer.get()); |
| } |
| if (status.code() == absl::StatusCode::kOutOfRange) { // EOF |
| EXPECT_EQ(0, buffer->size()); |
| } |
| return status; |
| } |
| |
| // A container for specification of instances of DummyStreamingAead |
| // to be created for testing. |
| struct StreamingAeadSpec { |
| uint32_t key_id; |
| std::string saead_name; |
| }; |
| |
| // Generates a PrimitiveSet<StreamingAead> with DummyStreamingAead |
| // instances according to the specification in 'spec'. |
| // The last entry in 'spec' will be the primary primitive in the returned set. |
| std::shared_ptr<PrimitiveSet<StreamingAead>> GetTestStreamingAeadSet( |
| const std::vector<StreamingAeadSpec>& spec) { |
| std::shared_ptr<PrimitiveSet<StreamingAead>> saead_set = |
| std::make_shared<PrimitiveSet<StreamingAead>>(); |
| int i = 0; |
| for (auto& s : spec) { |
| KeysetInfo::KeyInfo key_info; |
| key_info.set_output_prefix_type(OutputPrefixType::RAW); |
| key_info.set_key_id(s.key_id); |
| key_info.set_status(KeyStatusType::ENABLED); |
| std::unique_ptr<StreamingAead> saead = |
| absl::make_unique<DummyStreamingAead>(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(DecryptingRandomAccessStreamTest, BasicDecryption) { |
| 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}, {key_id_1, saead_name_1}, |
| {key_id_2, saead_name_2}}); |
| |
| 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, "'")); |
| // Pre-compute ciphertexts. We create one ciphertext for each primitive |
| // in the primitive set, so that we can test decryption with both |
| // the primary primitive, and the non-primary ones. |
| std::vector<std::unique_ptr<RandomAccessStream>> ciphertexts; |
| for (const auto& p : *(saead_set->get_raw_primitives().value())) { |
| ciphertexts.push_back( |
| GetCiphertextSource(&(p->get_primitive()), plaintext, aad)); |
| } |
| EXPECT_EQ(3, ciphertexts.size()); |
| |
| // Check the decryption of each of the pre-computed ciphertexts. |
| for (auto& ct : ciphertexts) { |
| // Wrap the primitive set and test the resulting |
| // DecryptingRandomAccessStream. |
| auto dec_stream_result = |
| DecryptingRandomAccessStream::New(saead_set, std::move(ct), aad); |
| EXPECT_THAT(dec_stream_result, IsOk()); |
| auto dec_stream = std::move(dec_stream_result.value()); |
| std::string decrypted; |
| auto status = ReadAll(dec_stream.get(), &decrypted); |
| EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange, |
| HasSubstr("EOF"))); |
| EXPECT_EQ(pt_size, dec_stream->size().value()); |
| EXPECT_EQ(plaintext, decrypted); |
| } |
| } |
| } |
| } |
| |
| TEST(DecryptingRandomAccessStreamTest, SelectiveDecryption) { |
| 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}, {key_id_1, saead_name_1}, |
| {key_id_2, saead_name_2}}); |
| |
| for (int pt_size : {5, 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, "'")); |
| // Pre-compute ciphertexts. We create one ciphertext for each primitive |
| // in the primitive set, so that we can test decryption with both |
| // the primary primitive, and the non-primary ones. |
| std::vector<std::unique_ptr<RandomAccessStream>> ciphertexts; |
| for (const auto& p : *(saead_set->get_raw_primitives().value())) { |
| ciphertexts.push_back( |
| GetCiphertextSource(&(p->get_primitive()), plaintext, aad)); |
| } |
| EXPECT_EQ(3, ciphertexts.size()); |
| |
| // Check the decryption of each of the pre-computed ciphertexts. |
| int ct_number = 0; |
| for (auto& ct : ciphertexts) { |
| // Wrap the primitive set and test the resulting |
| // DecryptingRandomAccessStream. |
| auto dec_stream_result = |
| DecryptingRandomAccessStream::New(saead_set, std::move(ct), aad); |
| EXPECT_THAT(dec_stream_result, IsOk()); |
| auto dec_stream = std::move(dec_stream_result.value()); |
| for (int position : {0, 1, 2, pt_size/2, pt_size-1}) { |
| for (int chunk_size : {1, pt_size/2, pt_size}) { |
| SCOPED_TRACE(absl::StrCat("ct_number = ", ct_number, |
| ", position = ", position, |
| ", chunk_size = ", chunk_size)); |
| auto buffer = std::move(util::Buffer::New(chunk_size).value()); |
| auto status = dec_stream->PRead(position, chunk_size, buffer.get()); |
| EXPECT_THAT(status, IsOk()); |
| EXPECT_EQ(std::min(chunk_size, pt_size - position), buffer->size()); |
| EXPECT_EQ(0, std::memcmp(plaintext.data() + position, |
| buffer->get_mem_block(), buffer->size())); |
| } |
| } |
| ct_number++; |
| } |
| } |
| } |
| } |
| |
| TEST(DecryptingRandomAccessStreamTest, OutOfRangeDecryption) { |
| 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}, {key_id_1, saead_name_1}, |
| {key_id_2, saead_name_2}}); |
| |
| for (int pt_size : {1, 5, 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, "'")); |
| // Pre-compute ciphertexts. We create one ciphertext for each primitive |
| // in the primitive set, so that we can test decryption with both |
| // the primary primitive, and the non-primary ones. |
| std::vector<std::unique_ptr<RandomAccessStream>> ciphertexts; |
| for (const auto& p : *(saead_set->get_raw_primitives().value())) { |
| ciphertexts.push_back( |
| GetCiphertextSource(&(p->get_primitive()), plaintext, aad)); |
| } |
| EXPECT_EQ(3, ciphertexts.size()); |
| |
| // Check the decryption of each of the pre-computed ciphertexts. |
| int ct_number = 0; |
| for (auto& ct : ciphertexts) { |
| // Wrap the primitive set and test the resulting |
| // DecryptingRandomAccessStream. |
| auto dec_stream_result = |
| DecryptingRandomAccessStream::New(saead_set, std::move(ct), aad); |
| EXPECT_THAT(dec_stream_result, IsOk()); |
| auto dec_stream = std::move(dec_stream_result.value()); |
| int chunk_size = 1; |
| auto buffer = std::move(util::Buffer::New(chunk_size).value()); |
| for (int position : {pt_size, pt_size + 1}) { |
| SCOPED_TRACE(absl::StrCat("ct_number = ", ct_number, |
| ", position = ", position)); |
| // Negative chunk size. |
| auto status = dec_stream->PRead(position, -1, buffer.get()); |
| EXPECT_THAT(status, StatusIs(absl::StatusCode::kInvalidArgument)); |
| |
| // Negative position. |
| status = dec_stream->PRead(-1, chunk_size, buffer.get()); |
| EXPECT_THAT(status, StatusIs(absl::StatusCode::kInvalidArgument)); |
| |
| // Reading past EOF. |
| status = dec_stream->PRead(position, chunk_size, buffer.get()); |
| EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange)); |
| } |
| ct_number++; |
| } |
| } |
| } |
| } |
| |
| TEST(DecryptingRandomAccessStreamTest, WrongAssociatedData) { |
| 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}, {key_id_1, saead_name_1}, |
| {key_id_2, saead_name_2}}); |
| |
| 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, "'")); |
| // Compute a ciphertext with the primary primitive. |
| auto ct = GetCiphertextSource( |
| &(saead_set->get_primary()->get_primitive()), plaintext, aad); |
| auto dec_stream_result = DecryptingRandomAccessStream::New( |
| saead_set, std::move(ct), "wrong aad"); |
| EXPECT_THAT(dec_stream_result, IsOk()); |
| std::string decrypted; |
| auto status = ReadAll(dec_stream_result.value().get(), &decrypted); |
| EXPECT_THAT(status, StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| } |
| } |
| |
| TEST(DecryptingRandomAccessStreamTest, WrongCiphertext) { |
| 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}, {key_id_1, saead_name_1}, |
| {key_id_2, saead_name_2}}); |
| |
| 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, "'")); |
| // Try decrypting a wrong ciphertext. |
| auto wrong_ct = |
| GetRandomAccessStream(subtle::Random::GetRandomBytes(pt_size)); |
| auto dec_stream_result = DecryptingRandomAccessStream::New( |
| saead_set, std::move(wrong_ct), aad); |
| EXPECT_THAT(dec_stream_result, IsOk()); |
| std::string decrypted; |
| auto status = ReadAll(dec_stream_result.value().get(), &decrypted); |
| EXPECT_THAT(status, StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| } |
| } |
| |
| TEST(DecryptingRandomAccessStreamTest, NullPrimitiveSet) { |
| auto ct_stream = GetRandomAccessStream("some ciphertext contents"); |
| auto dec_stream_result = DecryptingRandomAccessStream::New( |
| nullptr, std::move(ct_stream), "some aad"); |
| EXPECT_THAT(dec_stream_result.status(), |
| StatusIs(absl::StatusCode::kInvalidArgument, |
| HasSubstr("primitives must be non-null"))); |
| } |
| |
| TEST(DecryptingRandomAccessStreamTest, NullCiphertextSource) { |
| uint32_t key_id = 1234543; |
| std::string saead_name = "streaming_aead"; |
| auto saead_set = GetTestStreamingAeadSet({{key_id, saead_name}}); |
| |
| auto dec_stream_result = DecryptingRandomAccessStream::New( |
| saead_set, nullptr, "some aad"); |
| EXPECT_THAT(dec_stream_result.status(), |
| StatusIs(absl::StatusCode::kInvalidArgument, |
| HasSubstr("ciphertext_source must be non-null"))); |
| } |
| |
| } // namespace |
| } // namespace streamingaead |
| } // namespace tink |
| } // namespace crypto |