GCC Code Coverage Report


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