1 // Copyright 2019 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ///////////////////////////////////////////////////////////////////////////////
16
17 #include "tink/subtle/streaming_aead_encrypting_stream.h"
18
19 #include <algorithm>
20 #include <cstring>
21 #include <memory>
22 #include <utility>
23 #include <vector>
24
25 #include "absl/memory/memory.h"
26 #include "absl/status/status.h"
27 #include "tink/output_stream.h"
28 #include "tink/subtle/stream_segment_encrypter.h"
29 #include "tink/util/statusor.h"
30
31 using crypto::tink::OutputStream;
32 using crypto::tink::util::Status;
33 using crypto::tink::util::StatusOr;
34
35 namespace crypto {
36 namespace tink {
37 namespace subtle {
38
39 namespace {
40
41 // Writes 'contents' to the specified 'output_stream', which must be non-null.
42 // In case of errors returns the first non-OK status of
43 // output_stream->Next()-operation.
44
WriteToStream(const std::vector<uint8_t> & contents,OutputStream * output_stream)45 util::Status WriteToStream(const std::vector<uint8_t>& contents,
46 OutputStream* output_stream) {
47 void* buffer;
48 int pos = 0;
49 int remaining = contents.size();
50 int available_space = 0;
51 int available_bytes = 0;
52 while (remaining > 0) {
53 auto next_result = output_stream->Next(&buffer);
54 if (!next_result.ok()) return next_result.status();
55 available_space = next_result.value();
56 available_bytes = std::min(available_space, remaining);
57 memcpy(buffer, contents.data() + pos, available_bytes);
58 remaining -= available_bytes;
59 pos += available_bytes;
60 }
61 if (available_space > available_bytes) {
62 output_stream->BackUp(available_space - available_bytes);
63 }
64 return util::OkStatus();
65 }
66
67 } // anonymous namespace
68
69 // static
New(std::unique_ptr<StreamSegmentEncrypter> segment_encrypter,std::unique_ptr<OutputStream> ciphertext_destination)70 StatusOr<std::unique_ptr<OutputStream>> StreamingAeadEncryptingStream::New(
71 std::unique_ptr<StreamSegmentEncrypter> segment_encrypter,
72 std::unique_ptr<OutputStream> ciphertext_destination) {
73 if (segment_encrypter == nullptr) {
74 return Status(absl::StatusCode::kInvalidArgument,
75 "segment_encrypter must be non-null");
76 }
77 if (ciphertext_destination == nullptr) {
78 return Status(absl::StatusCode::kInvalidArgument,
79 "cipertext_destination must be non-null");
80 }
81 std::unique_ptr<StreamingAeadEncryptingStream> enc_stream(
82 new StreamingAeadEncryptingStream());
83 enc_stream->segment_encrypter_ = std::move(segment_encrypter);
84 enc_stream->ct_destination_ = std::move(ciphertext_destination);
85 int first_segment_size =
86 enc_stream->segment_encrypter_->get_plaintext_segment_size() -
87 enc_stream->segment_encrypter_->get_ciphertext_offset() -
88 enc_stream->segment_encrypter_->get_header().size();
89
90 if (first_segment_size <= 0) {
91 return Status(absl::StatusCode::kInternal,
92 "Size of the first segment must be greater than 0.");
93 }
94 enc_stream->pt_buffer_.resize(first_segment_size);
95 enc_stream->pt_to_encrypt_.resize(0);
96 enc_stream->position_ = 0;
97 enc_stream->is_first_segment_ = true;
98 enc_stream->count_backedup_ = first_segment_size;
99 enc_stream->pt_buffer_offset_ = 0;
100 enc_stream->status_ = util::OkStatus();
101 return {std::move(enc_stream)};
102 }
103
Next(void ** data)104 StatusOr<int> StreamingAeadEncryptingStream::Next(void** data) {
105 if (!status_.ok()) return status_;
106
107 // The first call to Next().
108 if (is_first_segment_) {
109 is_first_segment_ = false;
110 count_backedup_ = 0;
111 status_ =
112 WriteToStream(segment_encrypter_->get_header(), ct_destination_.get());
113 if (!status_.ok()) return status_;
114 *data = pt_buffer_.data();
115 position_ = pt_buffer_.size();
116 return pt_buffer_.size();
117 }
118
119 // If some space was backed up, return it first.
120 if (count_backedup_ > 0) {
121 position_ += count_backedup_;
122 pt_buffer_offset_ = pt_buffer_.size() - count_backedup_;
123 int backedup = count_backedup_;
124 count_backedup_ = 0;
125 *data = pt_buffer_.data() + pt_buffer_offset_;
126 return backedup;
127 }
128
129 // We're past the first segment, and no space was backed up, so we:
130 // 1. encrypt pt_to_encrypt_ (if non-empty) as a not-last segment
131 // and attempt to write the ciphertext to ct_destination_.
132 // 2. move contents of pt_buffer_ to pt_to_encrypt_ (for later encryption,
133 // as we don't know yet whether it will be the last segment or not.
134 // 3. prepare and return "fresh" pt_buffer_.
135 //
136 // Step 1.
137 if (!pt_to_encrypt_.empty()) {
138 status_ = segment_encrypter_->EncryptSegment(
139 pt_to_encrypt_, /* is_last_segment = */ false, &ct_buffer_);
140 if (!status_.ok()) return status_;
141 status_ = WriteToStream(ct_buffer_, ct_destination_.get());
142 if (!status_.ok()) return status_;
143 }
144 // Step 2.
145 pt_buffer_.swap(pt_to_encrypt_);
146 // Step 3.
147 pt_buffer_.resize(segment_encrypter_->get_plaintext_segment_size());
148 *data = pt_buffer_.data();
149 pt_buffer_offset_ = 0;
150 position_ += pt_buffer_.size();
151 return pt_buffer_.size();
152 }
153
BackUp(int count)154 void StreamingAeadEncryptingStream::BackUp(int count) {
155 if (is_first_segment_ || !status_.ok() || count < 1) return;
156 int curr_buffer_size = pt_buffer_.size() - pt_buffer_offset_;
157 int actual_count = std::min(count, curr_buffer_size - count_backedup_);
158 count_backedup_ += actual_count;
159 position_ -= actual_count;
160 }
161
Close()162 Status StreamingAeadEncryptingStream::Close() {
163 if (!status_.ok()) return status_;
164 if (is_first_segment_) { // Next() was never called.
165 status_ =
166 WriteToStream(segment_encrypter_->get_header(), ct_destination_.get());
167 if (!status_.ok()) return status_;
168 }
169
170 // The last segment encrypts plaintext from pt_to_encrypt_,
171 // unless the current pt_buffer_ has some plaintext bytes.
172 std::vector<uint8_t>* pt_last_segment = &pt_to_encrypt_;
173 if ((!pt_buffer_.empty()) && count_backedup_ < pt_buffer_.size()) {
174 // The last segment encrypts plaintext from pt_buffer_.
175 pt_buffer_.resize(pt_buffer_.size() - count_backedup_);
176 pt_last_segment = &pt_buffer_;
177 }
178 if (pt_last_segment != &pt_to_encrypt_ && (!pt_to_encrypt_.empty())) {
179 // Before writing the last segment we must encrypt pt_to_encrypt_.
180 status_ = segment_encrypter_->EncryptSegment(
181 pt_to_encrypt_, /* is_last_segment = */ false, &ct_buffer_);
182 if (!status_.ok()) {
183 ct_destination_->Close().IgnoreError();
184 return status_;
185 }
186 status_ = WriteToStream(ct_buffer_, ct_destination_.get());
187 if (!status_.ok()) {
188 ct_destination_->Close().IgnoreError();
189 return status_;
190 }
191 }
192
193 // Encrypt pt_last_segment, write the ciphertext, and close the stream.
194 status_ = segment_encrypter_->EncryptSegment(
195 *pt_last_segment, /* is_last_segment = */ true, &ct_buffer_);
196 if (!status_.ok()) {
197 ct_destination_->Close().IgnoreError();
198 return status_;
199 }
200 status_ = WriteToStream(ct_buffer_, ct_destination_.get());
201 if (!status_.ok()) {
202 ct_destination_->Close().IgnoreError();
203 return status_;
204 }
205 status_ = Status(absl::StatusCode::kFailedPrecondition, "Stream closed");
206 return ct_destination_->Close();
207 }
208
Position() const209 int64_t StreamingAeadEncryptingStream::Position() const { return position_; }
210
211 } // namespace subtle
212 } // namespace tink
213 } // namespace crypto
214