GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/crypto/encrypt.cc
Date: 2024-04-28 02:33:07
Exec Total Coverage
Lines: 168 180 93.3%
Branches: 99 177 55.9%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System
3 */
4
5 #include "cvmfs_config.h"
6 #include "crypto/encrypt.h"
7
8 #include <fcntl.h>
9 #include <openssl/evp.h>
10 #include <openssl/rand.h>
11 #include <unistd.h>
12
13 #include <cassert>
14 #include <cstdlib>
15 #include <cstring>
16 #include <ctime>
17
18 #include "crypto/hash.h"
19 #include "crypto/openssl_version.h"
20 #include "util/concurrency.h"
21 #include "util/exception.h"
22 #include "util/platform.h"
23 #include "util/pointer.h"
24 #include "util/smalloc.h"
25 #include "util/string.h"
26 #include "util/uuid.h"
27
28 using namespace std; // NOLINT
29
30 namespace cipher {
31
32 300027 Key *Key::CreateRandomly(const unsigned size) {
33 300027 Key *result = new Key();
34 300027 result->size_ = size;
35 300027 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
36 // TODO(jblomer): pin memory in RAM
37 300027 int retval = RAND_bytes(result->data_, result->size_);
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 300027 times.
300027 if (retval != 1) {
39 // Not enough entropy
40 delete result;
41 result = NULL;
42 }
43 300027 return result;
44 }
45
46
47 9 Key *Key::CreateFromFile(const string &path) {
48
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 int fd = open(path.c_str(), O_RDONLY);
49
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (fd < 0)
50 3 return NULL;
51 6 platform_disable_kcache(fd);
52
53 platform_stat64 info;
54 6 int retval = platform_fstat(fd, &info);
55
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (retval != 0) {
56 close(fd);
57 return NULL;
58 }
59
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
6 if ((info.st_size == 0) || (info.st_size > kMaxSize)) {
60
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 close(fd);
61 3 return NULL;
62 }
63
64
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 Key *result = new Key();
65 3 result->size_ = info.st_size;
66 3 result->data_ = reinterpret_cast<unsigned char *>(smalloc(result->size_));
67
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 int nbytes = read(fd, result->data_, result->size_);
68
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 close(fd);
69
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if ((nbytes < 0) || (static_cast<unsigned>(nbytes) != result->size_)) {
70 delete result;
71 result = NULL;
72 }
73 3 return result;
74 }
75
76
77 15 Key *Key::CreateFromString(const string &key) {
78 15 unsigned size = key.size();
79
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 9 times.
15 if ((size == 0) || (size > kMaxSize))
80 6 return NULL;
81
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
9 UniquePtr<Key> result(new Key());
82 9 result->size_ = size;
83 9 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
84 9 memcpy(result->data_, key.data(), size);
85 9 return result.Release();
86 9 }
87
88
89 300039 Key::~Key() {
90
1/2
✓ Branch 0 taken 300039 times.
✗ Branch 1 not taken.
300039 if (data_) {
91 300039 memset(data_, 0, size_);
92 300039 free(data_);
93 }
94 300039 }
95
96
97 6 bool Key::SaveToFile(const std::string &path) {
98 6 int fd = open(path.c_str(), O_WRONLY);
99
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (fd < 0)
100 3 return false;
101 3 platform_disable_kcache(fd);
102
103 3 int nbytes = write(fd, data_, size_);
104 3 close(fd);
105
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return (nbytes >= 0) && (static_cast<unsigned>(nbytes) == size_);
106 }
107
108
109 12 string Key::ToBase64() const {
110
2/4
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
24 return Base64(string(reinterpret_cast<const char *>(data_), size_));
111 }
112
113
114 //------------------------------------------------------------------------------
115
116
117 3 MemoryKeyDatabase::MemoryKeyDatabase() {
118 3 lock_ =
119 3 reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
120 3 int retval = pthread_mutex_init(lock_, NULL);
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 0);
122 3 }
123
124
125 6 MemoryKeyDatabase::~MemoryKeyDatabase() {
126 6 pthread_mutex_destroy(lock_);
127 6 free(lock_);
128 }
129
130
131 6 bool MemoryKeyDatabase::StoreNew(const Key *key, string *id) {
132 6 MutexLockGuard mutex_guard(lock_);
133 // TODO(jblomer): is this good enough for random keys? Salting? KDF2?
134
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 shash::Any hash(shash::kShake128);
135
1/2
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 HashMem(key->data(), key->size(), &hash);
136
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 *id = "H" + hash.ToString();
137
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 map<string, const Key *>::const_iterator i = database_.find(*id);
138
2/2
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
6 if (i != database_.end())
139 3 return false;
140
141
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 database_[*id] = key;
142 3 return true;
143 6 }
144
145
146 6 const Key *MemoryKeyDatabase::Find(const string &id) {
147 6 MutexLockGuard mutex_guard(lock_);
148
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 map<string, const Key *>::const_iterator i = database_.find(id);
149
2/2
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
6 if (i != database_.end())
150 3 return i->second;
151 3 return NULL;
152 6 }
153
154
155 //------------------------------------------------------------------------------
156
157
158 39 Cipher *Cipher::Create(const Algorithms a) {
159
2/3
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
39 switch (a) {
160 27 case kAes256Cbc:
161 27 return new CipherAes256Cbc();
162 12 case kNone:
163 12 return new CipherNone();
164 default:
165 PANIC(NULL);
166 }
167 // Never here
168 }
169
170
171 24 bool Cipher::Encrypt(
172 const string &plaintext,
173 const Key &key,
174 string *ciphertext)
175 {
176 24 ciphertext->clear();
177
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
24 if (key.size() != key_size())
178 return false;
179
180 24 unsigned char envelope = 0 & 0x0F;
181 24 envelope |= (algorithm() << 4) & 0xF0;
182 24 ciphertext->push_back(envelope);
183
184
1/2
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 *ciphertext += DoEncrypt(plaintext, key);
185 24 return true;
186 }
187
188
189 42 bool Cipher::Decrypt(
190 const string &ciphertext,
191 const Key &key,
192 string *plaintext)
193 {
194 42 plaintext->clear();
195
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 39 times.
42 if (ciphertext.size() < 1)
196 3 return false;
197 39 unsigned char envelope = ciphertext[0];
198 39 unsigned char version = envelope & 0x0F;
199
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 36 times.
39 if (version != 0)
200 3 return false;
201 36 unsigned char algorithm = (envelope & 0xF0) >> 4;
202
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 33 times.
36 if (algorithm > kNone)
203 3 return false;
204
205
2/4
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 33 times.
✗ Branch 5 not taken.
33 UniquePtr<Cipher> cipher(Create(static_cast<Algorithms>(algorithm)));
206
3/4
✓ Branch 3 taken 33 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 30 times.
33 if (key.size() != cipher->key_size())
207 3 return false;
208
3/6
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 30 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 30 times.
✗ Branch 9 not taken.
30 *plaintext += cipher->DoDecrypt(ciphertext.substr(1), key);
209 30 return true;
210 33 }
211
212
213 //------------------------------------------------------------------------------
214
215
216 21 string CipherAes256Cbc::DoDecrypt(const string &ciphertext, const Key &key) {
217
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
21 assert(key.size() == kKeySize);
218 int retval;
219
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 18 times.
21 if (ciphertext.size() < kIvSize)
220
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
221
222 const unsigned char *iv = reinterpret_cast<const unsigned char *>(
223 18 ciphertext.data());
224
225 // See OpenSSL documentation for the size
226 unsigned char *plaintext = reinterpret_cast<unsigned char *>(
227 18 smalloc(kBlockSize + ciphertext.size() - kIvSize));
228 int plaintext_len;
229 int tail_len;
230 #ifdef OPENSSL_API_INTERFACE_V11
231
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 EVP_CIPHER_CTX *ctx_ptr = EVP_CIPHER_CTX_new();
232 #else
233 EVP_CIPHER_CTX ctx;
234 EVP_CIPHER_CTX_init(&ctx);
235 EVP_CIPHER_CTX *ctx_ptr = &ctx;
236 #endif
237
2/4
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 6 not taken.
18 retval = EVP_DecryptInit_ex(ctx_ptr, EVP_aes_256_cbc(), NULL, key.data(), iv);
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 assert(retval == 1);
239 54 retval = EVP_DecryptUpdate(ctx_ptr,
240 plaintext, &plaintext_len,
241 reinterpret_cast<const unsigned char *>(
242
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 ciphertext.data() + kIvSize),
243 18 ciphertext.length() - kIvSize);
244
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (retval != 1) {
245 free(plaintext);
246 #ifdef OPENSSL_API_INTERFACE_V11
247 EVP_CIPHER_CTX_free(ctx_ptr);
248 #else
249 retval = EVP_CIPHER_CTX_cleanup(&ctx);
250 assert(retval == 1);
251 #endif
252 return "";
253 }
254
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 retval = EVP_DecryptFinal_ex(ctx_ptr, plaintext + plaintext_len, &tail_len);
255 #ifdef OPENSSL_API_INTERFACE_V11
256
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 EVP_CIPHER_CTX_free(ctx_ptr);
257 #else
258 int retval_2 = EVP_CIPHER_CTX_cleanup(&ctx);
259 assert(retval_2 == 1);
260 #endif
261
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 12 times.
18 if (retval != 1) {
262 6 free(plaintext);
263
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 return "";
264 }
265
266 12 plaintext_len += tail_len;
267
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (plaintext_len == 0) {
268 3 free(plaintext);
269
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
270 }
271
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 string result(reinterpret_cast<char *>(plaintext), plaintext_len);
272 9 free(plaintext);
273 9 return result;
274 9 }
275
276
277 18 string CipherAes256Cbc::DoEncrypt(const string &plaintext, const Key &key) {
278
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
18 assert(key.size() == kKeySize);
279 int retval;
280
281
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 shash::Md5 md5(GenerateIv(key));
282 // iv size happens to be md5 digest size
283 18 unsigned char *iv = md5.digest;
284
285 // See OpenSSL documentation as for the size. Additionally, we prepend the
286 // initialization vector.
287 unsigned char *ciphertext = reinterpret_cast<unsigned char *>(
288 18 smalloc(kIvSize + 2 * kBlockSize + plaintext.size()));
289 18 memcpy(ciphertext, iv, kIvSize);
290 18 int cipher_len = 0;
291 18 int tail_len = 0;
292 #ifdef OPENSSL_API_INTERFACE_V11
293
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 EVP_CIPHER_CTX *ctx_ptr = EVP_CIPHER_CTX_new();
294 #else
295 EVP_CIPHER_CTX ctx;
296 EVP_CIPHER_CTX_init(&ctx);
297 EVP_CIPHER_CTX *ctx_ptr = &ctx;
298 #endif
299
2/4
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 6 not taken.
18 retval = EVP_EncryptInit_ex(ctx_ptr, EVP_aes_256_cbc(), NULL, key.data(), iv);
300
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 assert(retval == 1);
301 // Older versions of OpenSSL don't allow empty input buffers
302
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 3 times.
18 if (!plaintext.empty()) {
303
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 retval = EVP_EncryptUpdate(ctx_ptr,
304 ciphertext + kIvSize, &cipher_len,
305 15 reinterpret_cast<const unsigned char *>(plaintext.data()),
306 15 plaintext.length());
307
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 assert(retval == 1);
308 }
309
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 retval = EVP_EncryptFinal_ex(ctx_ptr, ciphertext + kIvSize + cipher_len,
310 &tail_len);
311
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 assert(retval == 1);
312 #ifdef OPENSSL_API_INTERFACE_V11
313
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 EVP_CIPHER_CTX_free(ctx_ptr);
314 #else
315 retval = EVP_CIPHER_CTX_cleanup(&ctx);
316 assert(retval == 1);
317 #endif
318
319 18 cipher_len += tail_len;
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 assert(cipher_len > 0);
321
1/2
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
18 string result(reinterpret_cast<char *>(ciphertext), kIvSize + cipher_len);
322 18 free(ciphertext);
323 36 return result;
324 }
325
326
327 /**
328 * The block size of AES-256-CBC happens to be the same of the MD5 digest
329 * (128 bits). Use the HMAC of a UUID to make it random and unpredictable.
330 */
331 300018 shash::Md5 CipherAes256Cbc::GenerateIv(const Key &key) {
332 // The UUID is random but not necessarily cryptographically random. That
333 // saves the entropy pool.
334
3/6
✓ Branch 2 taken 300018 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 300018 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 300018 times.
✗ Branch 9 not taken.
600036 UniquePtr<cvmfs::Uuid> uuid(cvmfs::Uuid::Create(""));
335
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 300018 times.
300018 assert(uuid.IsValid());
336
337 // Now make it unpredictable, using an HMAC with the encryption key.
338
1/2
✓ Branch 1 taken 300018 times.
✗ Branch 2 not taken.
300018 shash::Any hmac(shash::kMd5);
339
2/4
✓ Branch 8 taken 300018 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 300018 times.
✗ Branch 12 not taken.
300018 shash::Hmac(string(reinterpret_cast<const char *>(key.data()), key.size()),
340 uuid->data(), uuid->size(), &hmac);
341
1/2
✓ Branch 1 taken 300018 times.
✗ Branch 2 not taken.
600036 return hmac.CastToMd5();
342 300018 }
343
344
345 //------------------------------------------------------------------------------
346
347
348 9 string CipherNone::DoDecrypt(const string &ciphertext, const Key &key) {
349 9 return ciphertext;
350 }
351
352
353 6 string CipherNone::DoEncrypt(const string &plaintext, const Key &key) {
354 6 return plaintext;
355 }
356
357 } // namespace cipher
358