/Users/deen/code/yugabyte-db/src/yb/encryption/ctr_cipher_stream.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include <openssl/evp.h> |
15 | | |
16 | | #include "yb/encryption/cipher_stream.h" |
17 | | #include "yb/encryption/encryption_util.h" |
18 | | |
19 | | #include "yb/gutil/casts.h" |
20 | | #include "yb/gutil/endian.h" |
21 | | |
22 | | #include "yb/util/status_format.h" |
23 | | |
24 | | namespace yb { |
25 | | namespace encryption { |
26 | | |
27 | | Result<std::unique_ptr<BlockAccessCipherStream>> BlockAccessCipherStream::FromEncryptionParams( |
28 | 160 | EncryptionParamsPtr encryption_params) { |
29 | 160 | auto stream = std::make_unique<BlockAccessCipherStream>(std::move(encryption_params)); |
30 | 160 | RETURN_NOT_OK(stream->Init()); |
31 | 160 | return stream; |
32 | 160 | } |
33 | | |
34 | | BlockAccessCipherStream::BlockAccessCipherStream( |
35 | | EncryptionParamsPtr encryption_params) : |
36 | | encryption_params_(std::move(encryption_params)), |
37 | 237 | encryption_context_(EVP_CIPHER_CTX_new(), [](EVP_CIPHER_CTX* ctx){ |
38 | 237 | EVP_CIPHER_CTX_cleanup(ctx); |
39 | 237 | EVP_CIPHER_CTX_free(ctx); |
40 | 237 | }) {} |
41 | | |
42 | 237 | Status BlockAccessCipherStream::Init() { |
43 | 237 | EVP_CIPHER_CTX_init(encryption_context_.get()); |
44 | 237 | const EVP_CIPHER* cipher; |
45 | 237 | switch (encryption_params_->key_size) { |
46 | 237 | case 16: |
47 | 237 | cipher = EVP_aes_128_ctr(); |
48 | 237 | break; |
49 | 0 | case 24: |
50 | 0 | cipher = EVP_aes_192_ctr(); |
51 | 0 | break; |
52 | 0 | case 32: |
53 | 0 | cipher = EVP_aes_256_ctr(); |
54 | 0 | break; |
55 | 0 | default: |
56 | 0 | return STATUS_SUBSTITUTE(IllegalState, "Expected key size to be one of 16, 24, 32, found $0.", |
57 | 237 | encryption_params_->key_size); |
58 | 237 | } |
59 | | |
60 | 237 | const auto encrypt_init_ex_result = EVP_EncryptInit_ex( |
61 | 237 | encryption_context_.get(), cipher, /* impl */ nullptr, /* key */ nullptr, |
62 | 237 | /* iv */ nullptr); |
63 | 237 | if (encrypt_init_ex_result != 1) { |
64 | 0 | return STATUS_FORMAT(InternalError, |
65 | 0 | "EVP_EncryptInit_ex returned $0", |
66 | 0 | encrypt_init_ex_result); |
67 | 0 | } |
68 | | |
69 | 237 | const auto set_padding_result = EVP_CIPHER_CTX_set_padding(encryption_context_.get(), 0); |
70 | 237 | if (set_padding_result != 1) { |
71 | 0 | return STATUS_FORMAT(InternalError, |
72 | 0 | "EVP_CIPHER_CTX_set_padding returned $0", |
73 | 0 | set_padding_result); |
74 | 0 | } |
75 | | |
76 | 237 | return Status::OK(); |
77 | 237 | } |
78 | | |
79 | | Status BlockAccessCipherStream::Encrypt( |
80 | | uint64_t file_offset, |
81 | | const Slice& input, |
82 | | void* output, |
83 | 601k | EncryptionOverflowWorkaround counter_overflow_workaround) { |
84 | 601k | if (!output) { |
85 | 0 | return STATUS(InvalidArgument, "output argument must be non-null."); |
86 | 0 | } |
87 | 601k | uint64_t data_size = input.size(); |
88 | 601k | if (data_size == 0) { |
89 | 351 | return Status::OK(); |
90 | 351 | } |
91 | | |
92 | 600k | uint64_t block_index = file_offset / EncryptionParams::kBlockSize; |
93 | 600k | uint64_t block_offset = file_offset % EncryptionParams::kBlockSize; |
94 | 600k | size_t first_block_size = 0; |
95 | | // Encrypt the first block alone if it is not byte aligned with the block size. |
96 | 600k | if (block_offset > 0) { |
97 | 562k | first_block_size = std::min(data_size, EncryptionParams::kBlockSize - block_offset); |
98 | 562k | uint8_t input_buf[EncryptionParams::kBlockSize]; |
99 | 562k | uint8_t output_buf[EncryptionParams::kBlockSize]; |
100 | 562k | memcpy(input_buf + block_offset, input.data(), first_block_size); |
101 | 562k | RETURN_NOT_OK(EncryptByBlock( |
102 | 562k | block_index, Slice(input_buf, EncryptionParams::kBlockSize), output_buf, |
103 | 562k | counter_overflow_workaround)); |
104 | 562k | memcpy(output, output_buf + block_offset, first_block_size); |
105 | 562k | block_index++; |
106 | 562k | data_size -= first_block_size; |
107 | 562k | } |
108 | | |
109 | | // Encrypt the rest of the data. |
110 | 600k | if (data_size > 0) { |
111 | 598k | RETURN_NOT_OK(EncryptByBlock(block_index, |
112 | 598k | Slice(input.data() + first_block_size, data_size), |
113 | 598k | static_cast<uint8_t*>(output) + first_block_size, |
114 | 598k | counter_overflow_workaround)); |
115 | 598k | } |
116 | 600k | return Status::OK(); |
117 | 600k | } |
118 | | |
119 | | // Decrypt data at the file offset. Since CTR mode uses symmetric XOR operation, |
120 | | // just calls Encrypt. |
121 | | Status BlockAccessCipherStream::Decrypt( |
122 | | uint64_t file_offset, const Slice& input, void* output, |
123 | 101k | EncryptionOverflowWorkaround counter_overflow_workaround) { |
124 | 101k | return Encrypt(file_offset, input, output, counter_overflow_workaround); |
125 | 101k | } |
126 | | |
127 | 487k | bool BlockAccessCipherStream::UseOpensslCompatibleCounterOverflow() { |
128 | 487k | return encryption_params_->openssl_compatible_counter_overflow; |
129 | 487k | } |
130 | | |
131 | | Status BlockAccessCipherStream::EncryptByBlock( |
132 | | uint64_t block_index, const Slice& input, void* output, |
133 | 1.16M | EncryptionOverflowWorkaround counter_overflow_workaround) { |
134 | 1.16M | if (input.size() == 0) { |
135 | 0 | return Status::OK(); |
136 | 0 | } |
137 | 1.16M | if (input.size() > implicit_cast<size_t>(std::numeric_limits<int>::max())) { |
138 | 0 | return STATUS_FORMAT(InternalError, |
139 | 0 | "Cannot encrypt/decrypt $0 bytes at once (it is more than $1 bytes). " |
140 | 0 | "Encryption block index: $2.", |
141 | 0 | input.size(), std::numeric_limits<int>::max(), block_index); |
142 | 0 | } |
143 | 1.16M | int data_size = narrow_cast<int>(input.size()); |
144 | | |
145 | | // Set the last 4 bytes of the iv based on counter + block_index. |
146 | 1.16M | uint8_t iv[EncryptionParams::kBlockSize]; |
147 | 1.16M | memcpy(iv, encryption_params_->nonce, EncryptionParams::kBlockSize - 4); |
148 | | |
149 | 1.16M | const uint64_t start_index = encryption_params_->counter + block_index; |
150 | 1.16M | IncrementCounter(start_index, iv, counter_overflow_workaround); |
151 | | |
152 | | // Lock the encryption op since we modify encryption context. |
153 | 1.16M | std::lock_guard<simple_spinlock> l(mutex_); |
154 | | |
155 | 1.16M | const int init_result = |
156 | 1.16M | EVP_EncryptInit_ex(encryption_context_.get(), /* cipher */ nullptr, /* impl */ nullptr, |
157 | 1.16M | encryption_params_->key, iv); |
158 | 1.16M | if (init_result != 1) { |
159 | 0 | return STATUS_FORMAT(InternalError, |
160 | 0 | "EVP_EncryptInit_ex returned $0 when encrypting/decrypting $1 bytes " |
161 | 0 | "at block index $2.", |
162 | 0 | init_result, data_size, block_index); |
163 | 0 | } |
164 | | |
165 | | // Perform the encryption. |
166 | 1.16M | int bytes_updated = 0; |
167 | 1.16M | const int update_result = EVP_EncryptUpdate( |
168 | 1.16M | encryption_context_.get(), static_cast<uint8_t*>(output), &bytes_updated, input.data(), |
169 | 1.16M | data_size); |
170 | 1.16M | if (update_result != 1) { |
171 | 0 | return STATUS_FORMAT(InternalError, |
172 | 0 | "EVP_EncryptUpdate returned $0 when encrypting/decrypting $1 bytes " |
173 | 0 | "at block index $2.", |
174 | 0 | update_result, data_size, block_index); |
175 | 0 | } |
176 | 1.16M | if (bytes_updated != data_size) { |
177 | 0 | return STATUS_FORMAT(InternalError, |
178 | 0 | "EVP_EncryptUpdate had to process $0 bytes but processed $1 bytes " |
179 | 0 | "instead. Encryption block index: $2.", |
180 | 0 | data_size, bytes_updated, block_index); |
181 | |
|
182 | 0 | } |
183 | | |
184 | 1.16M | return Status::OK(); |
185 | 1.16M | } |
186 | | |
187 | | void BlockAccessCipherStream::IncrementCounter( |
188 | | const uint64_t start_idx, uint8_t* iv, |
189 | 1.16M | EncryptionOverflowWorkaround counter_overflow_workaround) { |
190 | 1.16M | BigEndian::Store32(iv + EncryptionParams::kBlockSize - 4, static_cast<uint32_t>(start_idx)); |
191 | 1.16M | if (start_idx <= std::numeric_limits<uint32_t>::max() || |
192 | 1.16M | (387k !UseOpensslCompatibleCounterOverflow()387k && !counter_overflow_workaround193k )) { |
193 | 966k | return; |
194 | 966k | } |
195 | | |
196 | 194k | uint64_t carry = start_idx >> 32; |
197 | | |
198 | 404k | for (int i = 11; i >= 0 && carry != 0402k ; i--209k ) { |
199 | 209k | carry += iv[i]; |
200 | 209k | iv[i] = carry; |
201 | 209k | carry >>= 8; |
202 | 209k | } |
203 | 194k | } |
204 | | |
205 | | } // namespace encryption |
206 | | } // namespace yb |