YugabyteDB (2.13.1.0-b60, 21121d69985fbf76aa6958d8f04a9bfa936293b5)

Coverage Report

Created: 2022-03-22 16:43

/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