GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/crypto/encrypt.cc
Date: 2026-10-25 02:40:14
Exec Total Coverage
Lines: 159 166 95.8%
Branches: 98 163 60.1%

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 <nettle/aes.h>
10 #include <nettle/cbc.h>
11 #include <unistd.h>
12
13 #include <cassert>
14 #include <cstdlib>
15 #include <cstring>
16 #include <ctime>
17 #include <memory>
18
19 #include "crypto/hash.h"
20 #include "util/concurrency.h"
21 #include "util/exception.h"
22 #include "util/platform.h"
23 #include "util/smalloc.h"
24 #include "util/string.h"
25 #include "util/uuid.h"
26
27 using namespace std; // NOLINT
28
29 namespace cipher {
30
31 300035 Key *Key::CreateRandomly(const unsigned size) {
32 300035 Key *result = new Key();
33 300035 result->size_ = size;
34 300035 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
35 // TODO(jblomer): pin memory in RAM
36 300035 platform_getrandom(result->data_, result->size_);
37 300035 return result;
38 }
39
40
41 9 Key *Key::CreateFromFile(const string &path) {
42
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 const int fd = open(path.c_str(), O_RDONLY);
43
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (fd < 0)
44 3 return NULL;
45 6 platform_disable_kcache(fd);
46
47 platform_stat64 info;
48 6 const int retval = platform_fstat(fd, &info);
49
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (retval != 0) {
50 ✗ close(fd);
51 ✗ return NULL;
52 }
53
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)) {
54
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 close(fd);
55 3 return NULL;
56 }
57
58
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 Key *result = new Key();
59 3 result->size_ = info.st_size;
60 3 result->data_ = reinterpret_cast<unsigned char *>(smalloc(result->size_));
61
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 const int nbytes = read(fd, result->data_, result->size_);
62
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 close(fd);
63
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_)) {
64 ✗ delete result;
65 ✗ result = NULL;
66 }
67 3 return result;
68 }
69
70
71 19 Key *Key::CreateFromString(const string &key) {
72 19 const unsigned size = key.size();
73
4/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 13 times.
19 if ((size == 0) || (size > kMaxSize))
74 6 return NULL;
75
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 std::unique_ptr<Key> result(new Key());
76 13 result->size_ = size;
77 13 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
78 13 memcpy(result->data_, key.data(), size);
79 13 return result.release();
80 13 }
81
82
83 300051 Key::~Key() {
84
1/2
✓ Branch 0 taken 300051 times.
✗ Branch 1 not taken.
300051 if (data_) {
85 300051 memset(data_, 0, size_);
86 300051 free(data_);
87 }
88 300051 }
89
90
91 6 bool Key::SaveToFile(const std::string &path) {
92 6 const int fd = open(path.c_str(), O_WRONLY);
93
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (fd < 0)
94 3 return false;
95 3 platform_disable_kcache(fd);
96
97 3 const int nbytes = write(fd, data_, size_);
98 3 close(fd);
99
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_);
100 }
101
102
103 20 string Key::ToBase64() const {
104
2/4
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
40 return Base64(string(reinterpret_cast<const char *>(data_), size_));
105 }
106
107
108 //------------------------------------------------------------------------------
109
110
111 3 MemoryKeyDatabase::MemoryKeyDatabase() {
112 3 lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
113 3 const int retval = pthread_mutex_init(lock_, NULL);
114
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 0);
115 3 }
116
117
118 6 MemoryKeyDatabase::~MemoryKeyDatabase() {
119 6 pthread_mutex_destroy(lock_);
120 6 free(lock_);
121 }
122
123
124 6 bool MemoryKeyDatabase::StoreNew(const Key *key, string *id) {
125 6 const MutexLockGuard mutex_guard(lock_);
126 // TODO(jblomer): is this good enough for random keys? Salting? KDF2?
127
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 shash::Any hash(shash::kShake128);
128
1/2
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 HashMem(key->data(), key->size(), &hash);
129
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();
130
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const map<string, const Key *>::const_iterator i = database_.find(*id);
131
2/2
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
6 if (i != database_.end())
132 3 return false;
133
134
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 database_[*id] = key;
135 3 return true;
136 6 }
137
138
139 6 const Key *MemoryKeyDatabase::Find(const string &id) {
140 6 const MutexLockGuard mutex_guard(lock_);
141
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const map<string, const Key *>::const_iterator i = database_.find(id);
142
2/2
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 3 times.
6 if (i != database_.end())
143 3 return i->second;
144 3 return NULL;
145 6 }
146
147
148 //------------------------------------------------------------------------------
149
150
151 63 Cipher *Cipher::Create(const Algorithms a) {
152
2/3
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
63 switch (a) {
153 51 case kAes256Cbc:
154 51 return new CipherAes256Cbc();
155 12 case kNone:
156 12 return new CipherNone();
157 ✗ default:
158 ✗ PANIC(NULL);
159 }
160 // Never here
161 }
162
163
164 41 bool Cipher::Encrypt(const string &plaintext,
165 const Key &key,
166 string *ciphertext) {
167 41 ciphertext->clear();
168
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 41 times.
41 if (key.size() != key_size())
169 ✗ return false;
170
171 41 unsigned char envelope = 0 & 0x0F;
172 41 envelope |= (algorithm() << 4) & 0xF0;
173 41 ciphertext->push_back(envelope);
174
175
1/2
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
41 *ciphertext += DoEncrypt(plaintext, key);
176 41 return true;
177 }
178
179
180 58 bool Cipher::Decrypt(const string &ciphertext,
181 const Key &key,
182 string *plaintext) {
183 58 plaintext->clear();
184
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 55 times.
58 if (ciphertext.size() < 1)
185 3 return false;
186 55 const unsigned char envelope = ciphertext[0];
187 55 const unsigned char version = envelope & 0x0F;
188
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 52 times.
55 if (version != 0)
189 3 return false;
190 52 const unsigned char algorithm = (envelope & 0xF0) >> 4;
191
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 49 times.
52 if (algorithm > kNone)
192 3 return false;
193
194 const std::unique_ptr<Cipher> cipher(
195
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 Create(static_cast<Algorithms>(algorithm)));
196
3/4
✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 46 times.
49 if (key.size() != cipher->key_size())
197 3 return false;
198
3/6
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 46 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 46 times.
✗ Branch 9 not taken.
46 *plaintext += cipher->DoDecrypt(ciphertext.substr(1), key);
199 46 return true;
200 49 }
201
202
203 //------------------------------------------------------------------------------
204
205
206 37 string CipherAes256Cbc::DoDecrypt(const string &ciphertext, const Key &key) {
207
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 31 times.
37 if (ciphertext.length() <= kIvSize)
208
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 return "";
209
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 28 times.
31 if ((ciphertext.length() % AES_BLOCK_SIZE) != 0)
210
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
211
212 28 string plaintext;
213 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) cbc_ctx;
214
215
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
28 assert(key.size() == kKeySize);
216
1/2
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 aes256_set_decrypt_key(&cbc_ctx.ctx, key.data());
217
218 28 CBC_SET_IV(&cbc_ctx, ciphertext.data());
219
220
1/2
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 plaintext.resize(ciphertext.length() - kIvSize);
221
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
28 assert(plaintext.length() > 0);
222
223
2/4
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 28 times.
✗ Branch 7 not taken.
28 CBC_DECRYPT(&cbc_ctx, aes256_decrypt, plaintext.length(),
224 reinterpret_cast<uint8_t *>(&plaintext[0]),
225 reinterpret_cast<const uint8_t *>(ciphertext.data()) + kIvSize);
226
227 // PKCS#7: 1..16 padding bytes, all set to the padding length
228
1/2
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 const unsigned char padding_value = plaintext[plaintext.length() - 1];
229
3/4
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
28 if ((padding_value == 0) || (padding_value > AES_BLOCK_SIZE))
230
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
231
2/2
✓ Branch 0 taken 185 times.
✓ Branch 1 taken 22 times.
207 for (unsigned i = 2; i <= padding_value; ++i) {
232
1/2
✓ Branch 2 taken 185 times.
✗ Branch 3 not taken.
185 if (static_cast<unsigned char>(plaintext[plaintext.length() - i])
233
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 182 times.
185 != padding_value) {
234
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
235 }
236 }
237
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 plaintext.resize(plaintext.length() - padding_value);
238
239 22 return plaintext;
240 28 }
241
242
243 35 string CipherAes256Cbc::DoEncrypt(const string &plaintext, const Key &key) {
244 35 string ciphertext;
245 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) cbc_ctx;
246
247
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
35 assert(key.size() == kKeySize);
248
1/2
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
35 aes256_set_encrypt_key(&cbc_ctx.ctx, key.data());
249
250 // iv size happens to be md5 digest size
251
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 shash::Md5 md5(GenerateIv(key));
252 35 CBC_SET_IV(&cbc_ctx, md5.digest);
253
254 // cipher length: IV + plaintext length + padding
255 35 const size_t length_tail = plaintext.length() % AES_BLOCK_SIZE;
256 35 const size_t length_padding = AES_BLOCK_SIZE - length_tail;
257 35 const size_t length_cipher = AES_BLOCK_SIZE + plaintext.length()
258 35 + length_padding;
259
260
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 ciphertext.resize(length_cipher);
261
262
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 memcpy(&ciphertext[0], md5.digest, AES_BLOCK_SIZE);
263 // Encrypt all full blocks of the plain text
264
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 9 times.
35 if (plaintext.length() / AES_BLOCK_SIZE > 0) {
265
2/4
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
26 CBC_ENCRYPT(&cbc_ctx, aes256_encrypt,
266 AES_BLOCK_SIZE * (plaintext.length() / AES_BLOCK_SIZE),
267 reinterpret_cast<uint8_t *>(&ciphertext[0]) + AES_BLOCK_SIZE,
268 reinterpret_cast<const uint8_t *>(plaintext.data()));
269 }
270
271 // PKCS padding block
272
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 unsigned char *padding_block = reinterpret_cast<uint8_t *>(&ciphertext[0])
273 35 + ciphertext.length() - AES_BLOCK_SIZE;
274
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 9 times.
35 if (length_tail > 0) {
275 26 memcpy(padding_block, plaintext.data() + plaintext.length() - length_tail,
276 length_tail);
277 }
278 35 memset(padding_block + length_tail, length_padding, length_padding);
279
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
35 CBC_ENCRYPT(&cbc_ctx, aes256_encrypt, AES_BLOCK_SIZE, padding_block,
280 padding_block);
281
282 70 return ciphertext;
283 }
284
285
286 /**
287 * The block size of AES-256-CBC happens to be the same of the MD5 digest
288 * (128 bits). Use the HMAC of a UUID to make it random and unpredictable.
289 */
290 300035 shash::Md5 CipherAes256Cbc::GenerateIv(const Key &key) {
291 // The UUID is random but not necessarily cryptographically random. That
292 // saves the entropy pool.
293
2/4
✓ Branch 2 taken 300035 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 300035 times.
✗ Branch 6 not taken.
600070 const std::unique_ptr<cvmfs::Uuid> uuid(cvmfs::Uuid::Create(""));
294
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 300035 times.
300035 assert(uuid.get() != nullptr);
295
296 // Now make it unpredictable, using an HMAC with the encryption key.
297
1/2
✓ Branch 1 taken 300035 times.
✗ Branch 2 not taken.
300035 shash::Any hmac(shash::kMd5);
298
2/4
✓ Branch 8 taken 300035 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 300035 times.
✗ Branch 12 not taken.
300035 shash::Hmac(string(reinterpret_cast<const char *>(key.data()), key.size()),
299 uuid->data(), uuid->size(), &hmac);
300
1/2
✓ Branch 1 taken 300035 times.
✗ Branch 2 not taken.
600070 return hmac.CastToMd5();
301 300035 }
302
303
304 //------------------------------------------------------------------------------
305
306
307 9 string CipherNone::DoDecrypt(const string &ciphertext, const Key &key) {
308 9 return ciphertext;
309 }
310
311
312 6 string CipherNone::DoEncrypt(const string &plaintext, const Key &key) {
313 6 return plaintext;
314 }
315
316 } // namespace cipher
317