GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/crypto/encrypt.cc
Date: 2026-07-19 02:35:15
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 300039 Key *Key::CreateRandomly(const unsigned size) {
33 300039 Key *result = new Key();
34 300039 result->size_ = size;
35 300039 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
36 // TODO(jblomer): pin memory in RAM
37 300039 const int retval = RAND_bytes(result->data_, result->size_);
38
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 300039 times.
300039 if (retval != 1) {
39 // Not enough entropy
40 delete result;
41 result = NULL;
42 }
43 300039 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 const 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 const 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 const 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 21 Key *Key::CreateFromString(const string &key) {
78 21 const unsigned size = key.size();
79
4/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 15 times.
21 if ((size == 0) || (size > kMaxSize))
80 6 return NULL;
81
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
15 UniquePtr<Key> result(new Key());
82 15 result->size_ = size;
83 15 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
84 15 memcpy(result->data_, key.data(), size);
85 15 return result.Release();
86 15 }
87
88
89 300057 Key::~Key() {
90
1/2
✓ Branch 0 taken 300057 times.
✗ Branch 1 not taken.
300057 if (data_) {
91 300057 memset(data_, 0, size_);
92 300057 free(data_);
93 }
94 300057 }
95
96
97 6 bool Key::SaveToFile(const std::string &path) {
98 6 const 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 const 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 24 string Key::ToBase64() const {
110
2/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
48 return Base64(string(reinterpret_cast<const char *>(data_), size_));
111 }
112
113
114 //------------------------------------------------------------------------------
115
116
117 3 MemoryKeyDatabase::MemoryKeyDatabase() {
118 3 lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
119 3 const int retval = pthread_mutex_init(lock_, NULL);
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 0);
121 3 }
122
123
124 6 MemoryKeyDatabase::~MemoryKeyDatabase() {
125 6 pthread_mutex_destroy(lock_);
126 6 free(lock_);
127 }
128
129
130 6 bool MemoryKeyDatabase::StoreNew(const Key *key, string *id) {
131 6 const MutexLockGuard mutex_guard(lock_);
132 // TODO(jblomer): is this good enough for random keys? Salting? KDF2?
133
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 shash::Any hash(shash::kShake128);
134
1/2
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 HashMem(key->data(), key->size(), &hash);
135
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();
136
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const map<string, const Key *>::const_iterator i = database_.find(*id);
137
2/2
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
6 if (i != database_.end())
138 3 return false;
139
140
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 database_[*id] = key;
141 3 return true;
142 6 }
143
144
145 6 const Key *MemoryKeyDatabase::Find(const string &id) {
146 6 const MutexLockGuard mutex_guard(lock_);
147
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const map<string, const Key *>::const_iterator i = database_.find(id);
148
2/2
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
6 if (i != database_.end())
149 3 return i->second;
150 3 return NULL;
151 6 }
152
153
154 //------------------------------------------------------------------------------
155
156
157 57 Cipher *Cipher::Create(const Algorithms a) {
158
2/3
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
57 switch (a) {
159 45 case kAes256Cbc:
160 45 return new CipherAes256Cbc();
161 12 case kNone:
162 12 return new CipherNone();
163 default:
164 PANIC(NULL);
165 }
166 // Never here
167 }
168
169
170 36 bool Cipher::Encrypt(const string &plaintext,
171 const Key &key,
172 string *ciphertext) {
173 36 ciphertext->clear();
174
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 36 times.
36 if (key.size() != key_size())
175 return false;
176
177 36 unsigned char envelope = 0 & 0x0F;
178 36 envelope |= (algorithm() << 4) & 0xF0;
179 36 ciphertext->push_back(envelope);
180
181
1/2
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 *ciphertext += DoEncrypt(plaintext, key);
182 36 return true;
183 }
184
185
186 48 bool Cipher::Decrypt(const string &ciphertext,
187 const Key &key,
188 string *plaintext) {
189 48 plaintext->clear();
190
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 45 times.
48 if (ciphertext.size() < 1)
191 3 return false;
192 45 const unsigned char envelope = ciphertext[0];
193 45 const unsigned char version = envelope & 0x0F;
194
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 42 times.
45 if (version != 0)
195 3 return false;
196 42 const unsigned char algorithm = (envelope & 0xF0) >> 4;
197
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 39 times.
42 if (algorithm > kNone)
198 3 return false;
199
200
2/4
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 39 times.
✗ Branch 5 not taken.
39 const UniquePtr<Cipher> cipher(Create(static_cast<Algorithms>(algorithm)));
201
3/4
✓ Branch 3 taken 39 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 36 times.
39 if (key.size() != cipher->key_size())
202 3 return false;
203
3/6
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 36 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 36 times.
✗ Branch 9 not taken.
36 *plaintext += cipher->DoDecrypt(ciphertext.substr(1), key);
204 36 return true;
205 39 }
206
207
208 //------------------------------------------------------------------------------
209
210
211 27 string CipherAes256Cbc::DoDecrypt(const string &ciphertext, const Key &key) {
212
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
27 assert(key.size() == kKeySize);
213 int retval;
214
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 24 times.
27 if (ciphertext.size() < kIvSize)
215
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
216
217 const unsigned char *iv = reinterpret_cast<const unsigned char *>(
218 24 ciphertext.data());
219
220 // See OpenSSL documentation for the size
221 unsigned char *plaintext = reinterpret_cast<unsigned char *>(
222 24 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 24 times.
✗ Branch 2 not taken.
24 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 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
24 retval = EVP_DecryptInit_ex(ctx_ptr, EVP_aes_256_cbc(), NULL, key.data(), iv);
233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 assert(retval == 1);
234 72 retval = EVP_DecryptUpdate(
235 ctx_ptr, plaintext, &plaintext_len,
236
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 reinterpret_cast<const unsigned char *>(ciphertext.data() + kIvSize),
237 24 ciphertext.length() - kIvSize);
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 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 24 times.
✗ Branch 2 not taken.
24 retval = EVP_DecryptFinal_ex(ctx_ptr, plaintext + plaintext_len, &tail_len);
249 #ifdef OPENSSL_API_INTERFACE_V11
250
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 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 6 times.
✓ Branch 1 taken 18 times.
24 if (retval != 1) {
256 6 free(plaintext);
257
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 return "";
258 }
259
260 18 plaintext_len += tail_len;
261
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
18 if (plaintext_len == 0) {
262 3 free(plaintext);
263
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
264 }
265
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 string result(reinterpret_cast<char *>(plaintext), plaintext_len);
266 15 free(plaintext);
267 15 return result;
268 15 }
269
270
271 30 string CipherAes256Cbc::DoEncrypt(const string &plaintext, const Key &key) {
272
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
30 assert(key.size() == kKeySize);
273 int retval;
274
275
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 shash::Md5 md5(GenerateIv(key));
276 // iv size happens to be md5 digest size
277 30 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 30 smalloc(kIvSize + 2 * kBlockSize + plaintext.size()));
283 30 memcpy(ciphertext, iv, kIvSize);
284 30 int cipher_len = 0;
285 30 int tail_len = 0;
286 #ifdef OPENSSL_API_INTERFACE_V11
287
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 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 30 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 30 times.
✗ Branch 6 not taken.
30 retval = EVP_EncryptInit_ex(ctx_ptr, EVP_aes_256_cbc(), NULL, key.data(), iv);
294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 assert(retval == 1);
295 // Older versions of OpenSSL don't allow empty input buffers
296
2/2
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 3 times.
30 if (!plaintext.empty()) {
297
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 retval = EVP_EncryptUpdate(
298 ctx_ptr, ciphertext + kIvSize, &cipher_len,
299 27 reinterpret_cast<const unsigned char *>(plaintext.data()),
300 27 plaintext.length());
301
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 assert(retval == 1);
302 }
303
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 retval = EVP_EncryptFinal_ex(ctx_ptr, ciphertext + kIvSize + cipher_len,
304 &tail_len);
305
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 assert(retval == 1);
306 #ifdef OPENSSL_API_INTERFACE_V11
307
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 EVP_CIPHER_CTX_free(ctx_ptr);
308 #else
309 retval = EVP_CIPHER_CTX_cleanup(&ctx);
310 assert(retval == 1);
311 #endif
312
313 30 cipher_len += tail_len;
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 assert(cipher_len > 0);
315
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 string result(reinterpret_cast<char *>(ciphertext), kIvSize + cipher_len);
316 30 free(ciphertext);
317 60 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 300030 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 300030 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 300030 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 300030 times.
✗ Branch 9 not taken.
600060 const UniquePtr<cvmfs::Uuid> uuid(cvmfs::Uuid::Create(""));
329
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 300030 times.
300030 assert(uuid.IsValid());
330
331 // Now make it unpredictable, using an HMAC with the encryption key.
332
1/2
✓ Branch 1 taken 300030 times.
✗ Branch 2 not taken.
300030 shash::Any hmac(shash::kMd5);
333
2/4
✓ Branch 8 taken 300030 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 300030 times.
✗ Branch 12 not taken.
300030 shash::Hmac(string(reinterpret_cast<const char *>(key.data()), key.size()),
334 uuid->data(), uuid->size(), &hmac);
335
1/2
✓ Branch 1 taken 300030 times.
✗ Branch 2 not taken.
600060 return hmac.CastToMd5();
336 300030 }
337
338
339 //------------------------------------------------------------------------------
340
341
342 9 string CipherNone::DoDecrypt(const string &ciphertext, const Key &key) {
343 9 return ciphertext;
344 }
345
346
347 6 string CipherNone::DoEncrypt(const string &plaintext, const Key &key) {
348 6 return plaintext;
349 }
350
351 } // namespace cipher
352