blob: 75903c17dede84b0b02333d60e3c2d7ca04b8acc [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/streaming_aead_encrypting_stream.h"
#include <sstream>
#include <vector>
#include "gtest/gtest.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tink/output_stream.h"
#include "tink/subtle/stream_segment_encrypter.h"
#include "tink/subtle/random.h"
#include "tink/subtle/test_util.h"
#include "tink/util/ostream_output_stream.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
namespace crypto {
namespace tink {
namespace subtle {
using crypto::tink::OutputStream;
using crypto::tink::subtle::test::DummyStreamSegmentEncrypter;
using crypto::tink::util::OstreamOutputStream;
namespace {
// References to objects used for test validation.
// The objects pointed to are not owned by this structure.
struct ValidationRefs {
std::stringbuf* ct_buf; // buffer that contains the resulting ciphertext
DummyStreamSegmentEncrypter* seg_enc; // segment encrypter
};
// A helper for creating StreamingAeadEncryptingStream together
// with references to internal objects, used for test validation.
std::unique_ptr<OutputStream> GetEncryptingStream(
int pt_segment_size, int header_size, int ct_offset, ValidationRefs* refs) {
// Prepare ciphertext destination stream.
auto ct_stream = absl::make_unique<std::stringstream>();
// A reference to the ciphertext buffer, for later validation.
refs->ct_buf = ct_stream->rdbuf();
std::unique_ptr<OutputStream> ct_destination(
absl::make_unique<OstreamOutputStream>(std::move(ct_stream)));
auto seg_enc = absl::make_unique<DummyStreamSegmentEncrypter>(
pt_segment_size, header_size, ct_offset);
// A reference to the segment encrypter, for later validation.
refs->seg_enc = seg_enc.get();
auto enc_stream = std::move(StreamingAeadEncryptingStream::New(
std::move(seg_enc), std::move(ct_destination)).ValueOrDie());
EXPECT_EQ(0, enc_stream->Position());
return enc_stream;
}
class StreamingAeadEncryptingStreamTest : public ::testing::Test {
};
TEST_F(StreamingAeadEncryptingStreamTest, WritingStreams) {
std::vector<int> pt_sizes = {0, 10, 100, 1000, 10000, 100000, 1000000};
std::vector<int> pt_segment_sizes = {64, 100, 128, 1000, 1024};
std::vector<int> header_sizes = {5, 10, 32};
std::vector<int> ct_offsets = {0, 1, 5, 15};
for (auto pt_size : pt_sizes) {
for (auto pt_segment_size : pt_segment_sizes) {
for (auto header_size : header_sizes) {
for (auto ct_offset : ct_offsets) {
SCOPED_TRACE(absl::StrCat("pt_size = ", pt_size,
", pt_segment_size = ", pt_segment_size,
", header_size = ", header_size,
", ct_offset = ", ct_offset));
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(pt_segment_size, header_size,
ct_offset, &refs);
// First buffer returned by Next();
void* buffer;
auto next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
int buffer_size = next_result.ValueOrDie();
EXPECT_EQ(pt_segment_size - (header_size + ct_offset), buffer_size);
EXPECT_EQ(buffer_size, enc_stream->Position());
// Backup the entire first buffer.
enc_stream->BackUp(buffer_size);
EXPECT_EQ(0, enc_stream->Position());
// Write plaintext to the stream, and close the stream.
std::string pt = Random::GetRandomBytes(pt_size);
auto status = test::WriteToStream(enc_stream.get(), pt);
EXPECT_TRUE(status.ok()) << status;
EXPECT_EQ(enc_stream->Position(), pt.size());
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
auto exp_ciphertext = refs.seg_enc->GenerateCiphertext(pt);
EXPECT_EQ(exp_ciphertext.size(), refs.ct_buf->str().size());
EXPECT_EQ(exp_ciphertext, refs.ct_buf->str());
// Try closing the stream again.
status = enc_stream->Close();
EXPECT_FALSE(status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, status.error_code());
}
}
}
}
}
TEST_F(StreamingAeadEncryptingStreamTest, EmptyPlaintext) {
int pt_segment_size = 512;
int header_size = 64;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
// Close the stream.
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains only the header and an "empty" first segment.
EXPECT_EQ(header_size + DummyStreamSegmentEncrypter::kSegmentTagSize,
refs.ct_buf->str().size());
// The last segment is marked as such.
EXPECT_EQ(DummyStreamSegmentEncrypter::kLastSegment,
refs.ct_buf->str().back());
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
TEST_F(StreamingAeadEncryptingStreamTest, EmptyPlaintextWithBackup) {
int pt_segment_size = 512;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
// Get the first segment.
auto next_result = enc_stream->Next(&buffer);
int buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size, next_result.ValueOrDie());
EXPECT_EQ(buffer_size, enc_stream->Position());
// Backup the entire segment, and close the stream.
enc_stream->BackUp(buffer_size);
EXPECT_EQ(0, enc_stream->Position());
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains only the header and an "empty" first segment.
EXPECT_EQ(header_size + DummyStreamSegmentEncrypter::kSegmentTagSize,
refs.ct_buf->str().size());
// The last segment is marked as such.
EXPECT_EQ(DummyStreamSegmentEncrypter::kLastSegment,
refs.ct_buf->str().back());
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
TEST_F(StreamingAeadEncryptingStreamTest, OneSegmentPlaintext) {
int pt_segment_size = 512;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
// Get the first segment, and close the stream.
auto next_result = enc_stream->Next(&buffer);
int buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size, next_result.ValueOrDie());
EXPECT_EQ(buffer_size, enc_stream->Position());
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains only header and a full first segment.
EXPECT_EQ(pt_segment_size + DummyStreamSegmentEncrypter::kSegmentTagSize,
refs.ct_buf->str().size());
// The last segment is marked as such.
EXPECT_EQ(DummyStreamSegmentEncrypter::kLastSegment,
refs.ct_buf->str().back());
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
TEST_F(StreamingAeadEncryptingStreamTest, NextAfterBackup) {
int pt_segment_size = 512;
int part1_size = 123;
int part2_size = 74;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
// Get the first segment.
auto next_result = enc_stream->Next(&buffer);
int buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size, next_result.ValueOrDie());
EXPECT_EQ(buffer_size, enc_stream->Position());
// Backup so that only part1_size bytes are written.
enc_stream->BackUp(buffer_size - part1_size);
EXPECT_EQ(part1_size, enc_stream->Position());
// Get backed up space.
void* backedup_buffer;
next_result = enc_stream->Next(&backedup_buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size - part1_size, next_result.ValueOrDie());
EXPECT_EQ(reinterpret_cast<uint8_t*>(buffer) + part1_size,
reinterpret_cast<uint8_t*>(backedup_buffer));
// Backup so again that (part1_size + part2_size) bytes are written.
enc_stream->BackUp(buffer_size - (part1_size + part2_size));
EXPECT_EQ(part1_size + part2_size, enc_stream->Position());
// Get backed up space again.
next_result = enc_stream->Next(&backedup_buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size - (part1_size + part2_size), next_result.ValueOrDie());
EXPECT_EQ(reinterpret_cast<uint8_t*>(buffer) + part1_size + part2_size,
reinterpret_cast<uint8_t*>(backedup_buffer));
// Close the stream.
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
}
TEST_F(StreamingAeadEncryptingStreamTest, OneSegmentPlaintextWithBackup) {
int pt_segment_size = 512;
int pt_size = 200;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
// Get the first segment.
auto next_result = enc_stream->Next(&buffer);
int buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size, next_result.ValueOrDie());
EXPECT_EQ(buffer_size, enc_stream->Position());
// Backup so that only pt_size bytes are written, and close the stream.
enc_stream->BackUp(buffer_size - pt_size);
EXPECT_EQ(pt_size, enc_stream->Position());
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains only the header and partial first segment.
EXPECT_EQ(
header_size + pt_size + DummyStreamSegmentEncrypter::kSegmentTagSize,
refs.ct_buf->str().size());
// The last segment is marked as such.
EXPECT_EQ(DummyStreamSegmentEncrypter::kLastSegment,
refs.ct_buf->str().back());
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
TEST_F(StreamingAeadEncryptingStreamTest, ManySegmentsPlaintext) {
int pt_segment_size = 512;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
int seg_count = 5;
// Get the first segment.
auto next_result = enc_stream->Next(&buffer);
int first_buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(first_buffer_size, next_result.ValueOrDie());
EXPECT_EQ(first_buffer_size, enc_stream->Position());
// Get remaining segments.
for (int i = 1; i < seg_count; i++) {
next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(pt_segment_size, next_result.ValueOrDie());
EXPECT_EQ(first_buffer_size + i * pt_segment_size, enc_stream->Position());
}
// Close the stream.
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains seg_count full segments.
int ct_segment_size =
pt_segment_size + DummyStreamSegmentEncrypter::kSegmentTagSize;
EXPECT_EQ(refs.seg_enc->get_ciphertext_segment_size(), ct_segment_size);
EXPECT_EQ(ct_segment_size * seg_count, refs.ct_buf->str().size());
// The last segment is marked as such.
EXPECT_EQ(DummyStreamSegmentEncrypter::kLastSegment,
refs.ct_buf->str().back());
// The previous segments are marked as not-last ones.
for (int i = 1; i < seg_count - 1; i++) {
EXPECT_EQ(DummyStreamSegmentEncrypter::kNotLastSegment,
refs.ct_buf->str()[(ct_segment_size * i)-1]);
}
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
TEST_F(StreamingAeadEncryptingStreamTest, ManySegmentsPlaintextWithBackup) {
int pt_segment_size = 512;
int backup_size = 100;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
int seg_count = 5;
// Get the first segment.
auto next_result = enc_stream->Next(&buffer);
int first_buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(first_buffer_size, next_result.ValueOrDie());
EXPECT_EQ(first_buffer_size, enc_stream->Position());
// Get remaining segments.
for (int i = 1; i < seg_count; i++) {
next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(pt_segment_size, next_result.ValueOrDie());
EXPECT_EQ(first_buffer_size + i * pt_segment_size, enc_stream->Position());
}
// Backup part of the last segment, and close the stream.
enc_stream->BackUp(backup_size);
EXPECT_EQ(first_buffer_size + (seg_count - 1) * pt_segment_size - backup_size,
enc_stream->Position());
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains seg_count full segments, minus the size of the backup.
int ct_segment_size =
pt_segment_size + DummyStreamSegmentEncrypter::kSegmentTagSize;
EXPECT_EQ(refs.seg_enc->get_ciphertext_segment_size(), ct_segment_size);
EXPECT_EQ(ct_segment_size * seg_count - backup_size,
refs.ct_buf->str().size());
// The last segment is marked as such.
EXPECT_EQ(DummyStreamSegmentEncrypter::kLastSegment,
refs.ct_buf->str().back());
// The previous segments are marked as not-last ones.
for (int i = 1; i < seg_count - 1; i++) {
EXPECT_EQ(DummyStreamSegmentEncrypter::kNotLastSegment,
refs.ct_buf->str()[(ct_segment_size * i)-1]);
}
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
TEST_F(StreamingAeadEncryptingStreamTest, ManySegmentsPlaintextWithFullBackup) {
int pt_segment_size = 512;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
int seg_count = 5;
// Get the first segment.
auto next_result = enc_stream->Next(&buffer);
int first_buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(first_buffer_size, next_result.ValueOrDie());
EXPECT_EQ(first_buffer_size, enc_stream->Position());
// Get remaining segments.
for (int i = 1; i < seg_count; i++) {
next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(pt_segment_size, next_result.ValueOrDie());
EXPECT_EQ(first_buffer_size + i * pt_segment_size, enc_stream->Position());
}
// Backup the entire last segment, and close the stream.
enc_stream->BackUp(pt_segment_size);
EXPECT_EQ(first_buffer_size + (seg_count - 2) * pt_segment_size,
enc_stream->Position());
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains (seg_count - 1) full segments.
int ct_segment_size =
pt_segment_size + DummyStreamSegmentEncrypter::kSegmentTagSize;
EXPECT_EQ(refs.seg_enc->get_ciphertext_segment_size(), ct_segment_size);
EXPECT_EQ(ct_segment_size * (seg_count - 1), refs.ct_buf->str().size());
// The last segment is marked as such.
EXPECT_EQ(DummyStreamSegmentEncrypter::kLastSegment,
refs.ct_buf->str().back());
// The previous segments are marked as not-last ones.
for (int i = 1; i < seg_count - 1; i++) {
EXPECT_EQ(DummyStreamSegmentEncrypter::kNotLastSegment,
refs.ct_buf->str()[(ct_segment_size * i)-1]);
}
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
TEST_F(StreamingAeadEncryptingStreamTest, BackupAndPosition) {
int pt_segment_size = 512;
int header_size = 64;
void* buffer;
// Get an encrypting stream.
ValidationRefs refs;
auto enc_stream = GetEncryptingStream(
pt_segment_size, header_size, /* ct_offset = */ 0, &refs);
// The first buffer.
auto next_result = enc_stream->Next(&buffer);
int buffer_size = pt_segment_size - header_size;
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size, next_result.ValueOrDie());
EXPECT_EQ(buffer_size, enc_stream->Position());
// BackUp several times, but in total fewer bytes than returned by Next().
std::vector<int> backup_sizes = {0, 1, 5, 0, 10, 78, -42, 60, 120, -120};
int total_backup_size = 0;
for (auto backup_size : backup_sizes) {
enc_stream->BackUp(backup_size);
total_backup_size += std::max(0, backup_size);
EXPECT_EQ(buffer_size - total_backup_size, enc_stream->Position());
}
EXPECT_LT(total_backup_size, next_result.ValueOrDie());
// Call Next(), it should succeed (backuped bytes of 1st segment).
next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
EXPECT_EQ(buffer_size, enc_stream->Position());
// BackUp() some bytes, again fewer than returned by Next().
backup_sizes = {0, 72, -94, 37, 82};
total_backup_size = 0;
for (auto backup_size : backup_sizes) {
enc_stream->BackUp(backup_size);
total_backup_size += std::max(0, backup_size);
EXPECT_EQ(buffer_size - total_backup_size, enc_stream->Position());
}
EXPECT_LT(total_backup_size, next_result.ValueOrDie());
// Call Next(), it should succeed (backuped bytes of 1st segment).
next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
EXPECT_EQ(buffer_size, enc_stream->Position());
// Call Next() again, it should return a full segment (2nd segment).
auto prev_position = enc_stream->Position();
buffer_size = pt_segment_size;
next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size, next_result.ValueOrDie());
EXPECT_EQ(prev_position + buffer_size, enc_stream->Position());
// BackUp a few times, with total over the returned buffer_size.
backup_sizes = {0, 72, -100, buffer_size / 2, 200, -25, buffer_size / 2, 42};
total_backup_size = 0;
for (auto backup_size : backup_sizes) {
SCOPED_TRACE(absl::StrCat("backup_size = ", backup_size,
", total_backup_size = ", total_backup_size));
enc_stream->BackUp(backup_size);
total_backup_size = std::min(buffer_size,
total_backup_size + std::max(0, backup_size));
EXPECT_EQ(prev_position + buffer_size - total_backup_size,
enc_stream->Position());
}
EXPECT_EQ(total_backup_size, buffer_size);
EXPECT_EQ(prev_position, enc_stream->Position());
// Call Next() again, it should return a full segment (2nd segment);
next_result = enc_stream->Next(&buffer);
EXPECT_TRUE(next_result.ok()) << next_result.status();
EXPECT_EQ(buffer_size, next_result.ValueOrDie());
EXPECT_EQ(prev_position + buffer_size, enc_stream->Position());
EXPECT_EQ(2 * pt_segment_size - header_size, enc_stream->Position());
// Backup the entire segment, and close the stream.
enc_stream->BackUp(buffer_size);
EXPECT_EQ(pt_segment_size - header_size, enc_stream->Position());
auto close_status = enc_stream->Close();
EXPECT_TRUE(close_status.ok()) << close_status;
EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
refs.ct_buf->str().size());
// Ciphertext contains 1st segment (with header), and no traces
// of the "empty" (backed-up) segment.
EXPECT_EQ((pt_segment_size + DummyStreamSegmentEncrypter::kSegmentTagSize),
refs.ct_buf->str().size());
// Try closing the stream again.
close_status = enc_stream->Close();
EXPECT_FALSE(close_status.ok());
EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
}
} // namespace
} // namespace subtle
} // namespace tink
} // namespace crypto