GCC Code Coverage Report


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