GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/crypto/encrypt.cc
Date: 2025-05-11 02:35:43
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
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 500071 Key *Key::CreateRandomly(const unsigned size) {
33 500071 Key *result = new Key();
34 500071 result->size_ = size;
35 500071 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
36 // TODO(jblomer): pin memory in RAM
37 500071 int retval = RAND_bytes(result->data_, result->size_);
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 500071 times.
500071 if (retval != 1) {
39 // Not enough entropy
40 delete result;
41 result = NULL;
42 }
43 500071 return result;
44 }
45
46
47 15 Key *Key::CreateFromFile(const string &path) {
48
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 int fd = open(path.c_str(), O_RDONLY);
49
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 10 times.
15 if (fd < 0)
50 5 return NULL;
51 10 platform_disable_kcache(fd);
52
53 platform_stat64 info;
54 10 int retval = platform_fstat(fd, &info);
55
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (retval != 0) {
56 close(fd);
57 return NULL;
58 }
59
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
10 if ((info.st_size == 0) || (info.st_size > kMaxSize)) {
60
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 close(fd);
61 5 return NULL;
62 }
63
64
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 Key *result = new Key();
65 5 result->size_ = info.st_size;
66 5 result->data_ = reinterpret_cast<unsigned char *>(smalloc(result->size_));
67
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 int nbytes = read(fd, result->data_, result->size_);
68
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 close(fd);
69
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if ((nbytes < 0) || (static_cast<unsigned>(nbytes) != result->size_)) {
70 delete result;
71 result = NULL;
72 }
73 5 return result;
74 }
75
76
77 38 Key *Key::CreateFromString(const string &key) {
78 38 unsigned size = key.size();
79
4/4
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 28 times.
38 if ((size == 0) || (size > kMaxSize))
80 10 return NULL;
81
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 28 times.
✗ Branch 6 not taken.
28 UniquePtr<Key> result(new Key());
82 28 result->size_ = size;
83 28 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
84 28 memcpy(result->data_, key.data(), size);
85 28 return result.Release();
86 28 }
87
88
89 500104 Key::~Key() {
90
1/2
✓ Branch 0 taken 500104 times.
✗ Branch 1 not taken.
500104 if (data_) {
91 500104 memset(data_, 0, size_);
92 500104 free(data_);
93 }
94 500104 }
95
96
97 10 bool Key::SaveToFile(const std::string &path) {
98 10 int fd = open(path.c_str(), O_WRONLY);
99
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if (fd < 0)
100 5 return false;
101 5 platform_disable_kcache(fd);
102
103 5 int nbytes = write(fd, data_, size_);
104 5 close(fd);
105
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 return (nbytes >= 0) && (static_cast<unsigned>(nbytes) == size_);
106 }
107
108
109 46 string Key::ToBase64() const {
110
2/4
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 46 times.
✗ Branch 6 not taken.
92 return Base64(string(reinterpret_cast<const char *>(data_), size_));
111 }
112
113
114 //------------------------------------------------------------------------------
115
116
117 5 MemoryKeyDatabase::MemoryKeyDatabase() {
118 5 lock_ =
119 5 reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
120 5 int retval = pthread_mutex_init(lock_, NULL);
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(retval == 0);
122 5 }
123
124
125 10 MemoryKeyDatabase::~MemoryKeyDatabase() {
126 10 pthread_mutex_destroy(lock_);
127 10 free(lock_);
128 }
129
130
131 10 bool MemoryKeyDatabase::StoreNew(const Key *key, string *id) {
132 10 MutexLockGuard mutex_guard(lock_);
133 // TODO(jblomer): is this good enough for random keys? Salting? KDF2?
134
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 shash::Any hash(shash::kShake128);
135
1/2
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
10 HashMem(key->data(), key->size(), &hash);
136
2/4
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 *id = "H" + hash.ToString();
137
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 map<string, const Key *>::const_iterator i = database_.find(*id);
138
2/2
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 5 times.
10 if (i != database_.end())
139 5 return false;
140
141
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 database_[*id] = key;
142 5 return true;
143 10 }
144
145
146 10 const Key *MemoryKeyDatabase::Find(const string &id) {
147 10 MutexLockGuard mutex_guard(lock_);
148
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 map<string, const Key *>::const_iterator i = database_.find(id);
149
2/2
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 5 times.
10 if (i != database_.end())
150 5 return i->second;
151 5 return NULL;
152 10 }
153
154
155 //------------------------------------------------------------------------------
156
157
158 104 Cipher *Cipher::Create(const Algorithms a) {
159
2/3
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
104 switch (a) {
160 84 case kAes256Cbc:
161 84 return new CipherAes256Cbc();
162 20 case kNone:
163 20 return new CipherNone();
164 default:
165 PANIC(NULL);
166 }
167 // Never here
168 }
169
170
171 66 bool Cipher::Encrypt(
172 const string &plaintext,
173 const Key &key,
174 string *ciphertext)
175 {
176 66 ciphertext->clear();
177
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 66 times.
66 if (key.size() != key_size())
178 return false;
179
180 66 unsigned char envelope = 0 & 0x0F;
181 66 envelope |= (algorithm() << 4) & 0xF0;
182 66 ciphertext->push_back(envelope);
183
184
1/2
✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
66 *ciphertext += DoEncrypt(plaintext, key);
185 66 return true;
186 }
187
188
189 83 bool Cipher::Decrypt(
190 const string &ciphertext,
191 const Key &key,
192 string *plaintext)
193 {
194 83 plaintext->clear();
195
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 78 times.
83 if (ciphertext.size() < 1)
196 5 return false;
197 78 unsigned char envelope = ciphertext[0];
198 78 unsigned char version = envelope & 0x0F;
199
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 73 times.
78 if (version != 0)
200 5 return false;
201 73 unsigned char algorithm = (envelope & 0xF0) >> 4;
202
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 68 times.
73 if (algorithm > kNone)
203 5 return false;
204
205
2/4
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 68 times.
✗ Branch 5 not taken.
68 UniquePtr<Cipher> cipher(Create(static_cast<Algorithms>(algorithm)));
206
3/4
✓ Branch 3 taken 68 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 63 times.
68 if (key.size() != cipher->key_size())
207 5 return false;
208
3/6
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 63 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
63 *plaintext += cipher->DoDecrypt(ciphertext.substr(1), key);
209 63 return true;
210 68 }
211
212
213 //------------------------------------------------------------------------------
214
215
216 48 string CipherAes256Cbc::DoDecrypt(const string &ciphertext, const Key &key) {
217
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
48 assert(key.size() == kKeySize);
218 int retval;
219
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 43 times.
48 if (ciphertext.size() < kIvSize)
220
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 return "";
221
222 const unsigned char *iv = reinterpret_cast<const unsigned char *>(
223 43 ciphertext.data());
224
225 // See OpenSSL documentation for the size
226 unsigned char *plaintext = reinterpret_cast<unsigned char *>(
227 43 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 43 times.
✗ Branch 2 not taken.
43 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 43 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 43 times.
✗ Branch 6 not taken.
43 retval = EVP_DecryptInit_ex(ctx_ptr, EVP_aes_256_cbc(), NULL, key.data(), iv);
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 43 times.
43 assert(retval == 1);
239 129 retval = EVP_DecryptUpdate(ctx_ptr,
240 plaintext, &plaintext_len,
241 reinterpret_cast<const unsigned char *>(
242
1/2
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
43 ciphertext.data() + kIvSize),
243 43 ciphertext.length() - kIvSize);
244
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 43 times.
43 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 43 times.
✗ Branch 2 not taken.
43 retval = EVP_DecryptFinal_ex(ctx_ptr, plaintext + plaintext_len, &tail_len);
255 #ifdef OPENSSL_API_INTERFACE_V11
256
1/2
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
43 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 10 times.
✓ Branch 1 taken 33 times.
43 if (retval != 1) {
262 10 free(plaintext);
263
1/2
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 return "";
264 }
265
266 33 plaintext_len += tail_len;
267
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 28 times.
33 if (plaintext_len == 0) {
268 5 free(plaintext);
269
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 return "";
270 }
271
1/2
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 string result(reinterpret_cast<char *>(plaintext), plaintext_len);
272 28 free(plaintext);
273 28 return result;
274 28 }
275
276
277 56 string CipherAes256Cbc::DoEncrypt(const string &plaintext, const Key &key) {
278
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
56 assert(key.size() == kKeySize);
279 int retval;
280
281
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
56 shash::Md5 md5(GenerateIv(key));
282 // iv size happens to be md5 digest size
283 56 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 56 smalloc(kIvSize + 2 * kBlockSize + plaintext.size()));
289 56 memcpy(ciphertext, iv, kIvSize);
290 56 int cipher_len = 0;
291 56 int tail_len = 0;
292 #ifdef OPENSSL_API_INTERFACE_V11
293
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
56 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 56 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 56 times.
✗ Branch 6 not taken.
56 retval = EVP_EncryptInit_ex(ctx_ptr, EVP_aes_256_cbc(), NULL, key.data(), iv);
300
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 assert(retval == 1);
301 // Older versions of OpenSSL don't allow empty input buffers
302
2/2
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 5 times.
56 if (!plaintext.empty()) {
303
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 retval = EVP_EncryptUpdate(ctx_ptr,
304 ciphertext + kIvSize, &cipher_len,
305 51 reinterpret_cast<const unsigned char *>(plaintext.data()),
306 51 plaintext.length());
307
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
51 assert(retval == 1);
308 }
309
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
56 retval = EVP_EncryptFinal_ex(ctx_ptr, ciphertext + kIvSize + cipher_len,
310 &tail_len);
311
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 assert(retval == 1);
312 #ifdef OPENSSL_API_INTERFACE_V11
313
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
56 EVP_CIPHER_CTX_free(ctx_ptr);
314 #else
315 retval = EVP_CIPHER_CTX_cleanup(&ctx);
316 assert(retval == 1);
317 #endif
318
319 56 cipher_len += tail_len;
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 assert(cipher_len > 0);
321
1/2
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
56 string result(reinterpret_cast<char *>(ciphertext), kIvSize + cipher_len);
322 56 free(ciphertext);
323 112 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 500056 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 500056 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 500056 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 500056 times.
✗ Branch 9 not taken.
1000112 UniquePtr<cvmfs::Uuid> uuid(cvmfs::Uuid::Create(""));
335
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 500056 times.
500056 assert(uuid.IsValid());
336
337 // Now make it unpredictable, using an HMAC with the encryption key.
338
1/2
✓ Branch 1 taken 500056 times.
✗ Branch 2 not taken.
500056 shash::Any hmac(shash::kMd5);
339
2/4
✓ Branch 8 taken 500056 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 500056 times.
✗ Branch 12 not taken.
500056 shash::Hmac(string(reinterpret_cast<const char *>(key.data()), key.size()),
340 uuid->data(), uuid->size(), &hmac);
341
1/2
✓ Branch 1 taken 500056 times.
✗ Branch 2 not taken.
1000112 return hmac.CastToMd5();
342 500056 }
343
344
345 //------------------------------------------------------------------------------
346
347
348 15 string CipherNone::DoDecrypt(const string &ciphertext, const Key &key) {
349 15 return ciphertext;
350 }
351
352
353 10 string CipherNone::DoEncrypt(const string &plaintext, const Key &key) {
354 10 return plaintext;
355 }
356
357 } // namespace cipher
358