GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/crypto/encrypt.cc
Date: 2026-08-09 02:40:25
Exec Total Coverage
Lines: 158 165 95.8%
Branches: 101 169 59.8%

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
18 #include "crypto/hash.h"
19 #include "util/concurrency.h"
20 #include "util/exception.h"
21 #include "util/platform.h"
22 #include "util/pointer.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 300039 Key *Key::CreateRandomly(const unsigned size) {
32 300039 Key *result = new Key();
33 300039 result->size_ = size;
34 300039 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
35 // TODO(jblomer): pin memory in RAM
36 300039 platform_getrandom(result->data_, result->size_);
37 300039 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 21 Key *Key::CreateFromString(const string &key) {
72 21 const unsigned size = key.size();
73
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))
74 6 return NULL;
75
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());
76 15 result->size_ = size;
77 15 result->data_ = reinterpret_cast<unsigned char *>(smalloc(size));
78 15 memcpy(result->data_, key.data(), size);
79 15 return result.Release();
80 15 }
81
82
83 300057 Key::~Key() {
84
1/2
✓ Branch 0 taken 300057 times.
✗ Branch 1 not taken.
300057 if (data_) {
85 300057 memset(data_, 0, size_);
86 300057 free(data_);
87 }
88 300057 }
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 24 string Key::ToBase64() const {
104
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_));
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 69 Cipher *Cipher::Create(const Algorithms a) {
152
2/3
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
69 switch (a) {
153 57 case kAes256Cbc:
154 57 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 45 bool Cipher::Encrypt(const string &plaintext,
165 const Key &key,
166 string *ciphertext) {
167 45 ciphertext->clear();
168
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 45 times.
45 if (key.size() != key_size())
169 return false;
170
171 45 unsigned char envelope = 0 & 0x0F;
172 45 envelope |= (algorithm() << 4) & 0xF0;
173 45 ciphertext->push_back(envelope);
174
175
1/2
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
45 *ciphertext += DoEncrypt(plaintext, key);
176 45 return true;
177 }
178
179
180 60 bool Cipher::Decrypt(const string &ciphertext,
181 const Key &key,
182 string *plaintext) {
183 60 plaintext->clear();
184
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 57 times.
60 if (ciphertext.size() < 1)
185 3 return false;
186 57 const unsigned char envelope = ciphertext[0];
187 57 const unsigned char version = envelope & 0x0F;
188
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 54 times.
57 if (version != 0)
189 3 return false;
190 54 const unsigned char algorithm = (envelope & 0xF0) >> 4;
191
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 51 times.
54 if (algorithm > kNone)
192 3 return false;
193
194
2/4
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 51 times.
✗ Branch 5 not taken.
51 const UniquePtr<Cipher> cipher(Create(static_cast<Algorithms>(algorithm)));
195
3/4
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 48 times.
51 if (key.size() != cipher->key_size())
196 3 return false;
197
3/6
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 48 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 48 times.
✗ Branch 9 not taken.
48 *plaintext += cipher->DoDecrypt(ciphertext.substr(1), key);
198 48 return true;
199 51 }
200
201
202 //------------------------------------------------------------------------------
203
204
205 39 string CipherAes256Cbc::DoDecrypt(const string &ciphertext, const Key &key) {
206
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 33 times.
39 if (ciphertext.length() <= kIvSize)
207
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 return "";
208
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 30 times.
33 if ((ciphertext.length() % AES_BLOCK_SIZE) != 0)
209
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
210
211 30 string plaintext;
212 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) cbc_ctx;
213
214
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
30 assert(key.size() == kKeySize);
215
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 aes256_set_decrypt_key(&cbc_ctx.ctx, key.data());
216
217 30 CBC_SET_IV(&cbc_ctx, ciphertext.data());
218
219
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 plaintext.resize(ciphertext.length() - kIvSize);
220
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
30 assert(plaintext.length() > 0);
221
222
2/4
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
30 CBC_DECRYPT(&cbc_ctx, aes256_decrypt, plaintext.length(),
223 reinterpret_cast<uint8_t *>(&plaintext[0]),
224 reinterpret_cast<const uint8_t *>(ciphertext.data()) + kIvSize);
225
226 // PKCS#7: 1..16 padding bytes, all set to the padding length
227
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 const unsigned char padding_value = plaintext[plaintext.length() - 1];
228
3/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
30 if ((padding_value == 0) || (padding_value > AES_BLOCK_SIZE))
229
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
230
2/2
✓ Branch 0 taken 189 times.
✓ Branch 1 taken 24 times.
213 for (unsigned i = 2; i <= padding_value; ++i) {
231
3/4
✓ Branch 2 taken 189 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 186 times.
189 if (static_cast<unsigned char>(plaintext[plaintext.length() - i]) !=
232 padding_value) {
233
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return "";
234 }
235 }
236
1/2
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 plaintext.resize(plaintext.length() - padding_value);
237
238 24 return plaintext;
239 30 }
240
241
242 39 string CipherAes256Cbc::DoEncrypt(const string &plaintext, const Key &key) {
243 39 string ciphertext;
244 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) cbc_ctx;
245
246
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
39 assert(key.size() == kKeySize);
247
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 aes256_set_encrypt_key(&cbc_ctx.ctx, key.data());
248
249 // iv size happens to be md5 digest size
250
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 shash::Md5 md5(GenerateIv(key));
251 39 CBC_SET_IV(&cbc_ctx, md5.digest);
252
253 // cipher length: IV + plaintext length + padding
254 39 const size_t length_tail = plaintext.length() % AES_BLOCK_SIZE;
255 39 const size_t length_padding = AES_BLOCK_SIZE - length_tail;
256 39 const size_t length_cipher = AES_BLOCK_SIZE + plaintext.length() +
257 39 length_padding;
258
259
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 ciphertext.resize(length_cipher);
260
261
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 memcpy(&ciphertext[0], md5.digest, AES_BLOCK_SIZE);
262 // Encrypt all full blocks of the plain text
263
2/2
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 9 times.
39 if (plaintext.length() / AES_BLOCK_SIZE > 0) {
264
2/4
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
30 CBC_ENCRYPT(&cbc_ctx, aes256_encrypt,
265 AES_BLOCK_SIZE * (plaintext.length() / AES_BLOCK_SIZE),
266 reinterpret_cast<uint8_t *>(&ciphertext[0]) + AES_BLOCK_SIZE,
267 reinterpret_cast<const uint8_t *>(plaintext.data()));
268 }
269
270 // PKCS padding block
271
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 unsigned char *padding_block = reinterpret_cast<uint8_t *>(&ciphertext[0])
272 39 + ciphertext.length() - AES_BLOCK_SIZE;
273
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 9 times.
39 if (length_tail > 0) {
274 30 memcpy(padding_block, plaintext.data() + plaintext.length() - length_tail,
275 length_tail);
276 }
277 39 memset(padding_block + length_tail, length_padding, length_padding);
278
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 CBC_ENCRYPT(&cbc_ctx, aes256_encrypt, AES_BLOCK_SIZE,
279 padding_block, padding_block);
280
281 78 return ciphertext;
282 }
283
284
285 /**
286 * The block size of AES-256-CBC happens to be the same of the MD5 digest
287 * (128 bits). Use the HMAC of a UUID to make it random and unpredictable.
288 */
289 300039 shash::Md5 CipherAes256Cbc::GenerateIv(const Key &key) {
290 // The UUID is random but not necessarily cryptographically random. That
291 // saves the entropy pool.
292
3/6
✓ Branch 2 taken 300039 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 300039 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 300039 times.
✗ Branch 9 not taken.
600078 const UniquePtr<cvmfs::Uuid> uuid(cvmfs::Uuid::Create(""));
293
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 300039 times.
300039 assert(uuid.IsValid());
294
295 // Now make it unpredictable, using an HMAC with the encryption key.
296
1/2
✓ Branch 1 taken 300039 times.
✗ Branch 2 not taken.
300039 shash::Any hmac(shash::kMd5);
297
2/4
✓ Branch 8 taken 300039 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 300039 times.
✗ Branch 12 not taken.
300039 shash::Hmac(string(reinterpret_cast<const char *>(key.data()), key.size()),
298 uuid->data(), uuid->size(), &hmac);
299
1/2
✓ Branch 1 taken 300039 times.
✗ Branch 2 not taken.
600078 return hmac.CastToMd5();
300 300039 }
301
302
303 //------------------------------------------------------------------------------
304
305
306 9 string CipherNone::DoDecrypt(const string &ciphertext, const Key &key) {
307 9 return ciphertext;
308 }
309
310
311 6 string CipherNone::DoEncrypt(const string &plaintext, const Key &key) {
312 6 return plaintext;
313 }
314
315 } // namespace cipher
316