Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief filter plugin providing cryptographic operations
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #ifndef HAVE_KDBCONFIG
11 : #include "kdbconfig.h"
12 : #endif
13 : #include "crypto.h"
14 : #include "crypto_kdb_functions.h"
15 : #ifdef ELEKTRA_CRYPTO_API_GCRYPT
16 : #include "gcrypt_operations.h"
17 : #endif
18 : #ifdef ELEKTRA_CRYPTO_API_OPENSSL
19 : #include "openssl_operations.h"
20 : #endif
21 : #ifdef ELEKTRA_CRYPTO_API_BOTAN
22 : #include "botan_operations.h"
23 : #endif
24 : #include "gpg.h"
25 : #include "helper.h"
26 : #include <kdb.h>
27 : #include <kdberrors.h>
28 : #include <kdbtypes.h>
29 : #include <pthread.h>
30 : #include <stdlib.h>
31 : #include <string.h>
32 :
33 : static pthread_mutex_t mutex_ref_cnt = PTHREAD_MUTEX_INITIALIZER;
34 : static unsigned int ref_cnt = 0;
35 :
36 : // gurads against compiler warnings because the functions are only used within the specified compile variants
37 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT) || defined(ELEKTRA_CRYPTO_API_OPENSSL) || defined(ELEKTRA_CRYPTO_API_BOTAN)
38 :
39 : /**
40 : * @brief checks if a Key has been marked for encryption by checking the Key's metadata.
41 : *
42 : * If the metakey ELEKTRA_CRYPTO_META_ENCRYPT has the value "1" it is considered to be true.
43 : * Every other value or the non-existence of the metakey is considered to be false.
44 : *
45 : * @param k the Key to be checked
46 : * @retval 0 if the Key has not been marked for encryption
47 : * @retval 1 if the Key has been marked for encryption
48 : */
49 16 : static int isMarkedForEncryption (const Key * k)
50 : {
51 16 : const Key * metaEncrypt = keyGetMeta (k, ELEKTRA_CRYPTO_META_ENCRYPT);
52 16 : if (metaEncrypt && strcmp (keyString (metaEncrypt), "1") == 0)
53 : {
54 : return 1;
55 : }
56 : return 0;
57 : }
58 :
59 : /**
60 : * @brief checks if a given Key k is in the spec namespace.
61 : * @retval 0 if the Key k is in the spec namespace.
62 : * @retval 1 if the Key k is NOT in the spec namespace.
63 : */
64 : static inline int isSpecNamespace (const Key * k)
65 : {
66 12 : return (keyGetNamespace (k) == KEY_NS_SPEC);
67 : }
68 :
69 : #endif
70 :
71 : /**
72 : * @brief verify the version of the cryptographic payload of the given key.
73 : * @param k holds the encrypted payload.
74 : * @param errorKey holds an error description if the version does not match or the format is wrong at all.
75 : * @return 1 if the payload version could be verified.
76 : * @return 0 otherwise.
77 : */
78 6 : static int checkPayloadVersion (Key * k, Key * errorKey)
79 : {
80 6 : if (keyGetValueSize (k) < ((ssize_t) ELEKTRA_CRYPTO_MAGIC_NUMBER_LEN))
81 : {
82 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (
83 : errorKey,
84 : "The provided data could not be recognized as valid cryptographic payload. The data is possibly "
85 : "corrupted. Keyname: %s",
86 : keyName (k));
87 0 : return 0; // failure
88 : }
89 :
90 : // check the magic number without the version
91 6 : const kdb_octet_t * value = (kdb_octet_t *) keyValue (k);
92 6 : if (memcmp (value, ELEKTRA_CRYPTO_MAGIC_NUMBER, ELEKTRA_CRYPTO_MAGIC_NUMBER_LEN - 2))
93 : {
94 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (
95 : errorKey,
96 : "The provided data could not be recognized as valid cryptographic payload. The data is possibly "
97 : "corrupted. Keyname: %s",
98 : keyName (k));
99 0 : return 0; // failure
100 : }
101 :
102 : // check the version
103 6 : const size_t versionOffset = ELEKTRA_CRYPTO_MAGIC_NUMBER_LEN - 2;
104 6 : if (memcmp (&value[versionOffset], ELEKTRA_CRYPTO_PAYLOAD_VERSION, 2))
105 : {
106 0 : ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (
107 : errorKey, "The version of the cryptographic payload is not compatible with the version of the plugin. Keyname: %s",
108 : keyName (k));
109 0 : return 0; // failure
110 : }
111 :
112 : return 1; // success
113 : }
114 :
115 : /**
116 : * @brief initialize the crypto backend
117 : * @retval 1 on success
118 : * @retval -1 on failure
119 : */
120 : static int elektraCryptoInit (Key * errorKey ELEKTRA_UNUSED)
121 : {
122 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT)
123 17 : return elektraCryptoGcryInit (errorKey);
124 : #elif defined(ELEKTRA_CRYPTO_API_OPENSSL)
125 16 : return elektraCryptoOpenSSLInit (errorKey);
126 : #elif defined(ELEKTRA_CRYPTO_API_BOTAN)
127 16 : return elektraCryptoBotanInit (errorKey);
128 : #else
129 : return 1;
130 : #endif
131 : }
132 :
133 : /**
134 : * @brief clean up the crypto backend
135 : *
136 : * Some libraries may need extra code for cleaning up the environment.
137 : */
138 : static void elektraCryptoTeardown (void)
139 : {
140 : }
141 :
142 : /**
143 : * @brief read the plugin configuration for the supposed length of the master password.
144 : * @param errorKey may hold a warning if the provided configuration is invalid
145 : * @param conf the plugin configuration
146 : * @return the expected length of the master password
147 : */
148 1 : static kdb_unsigned_short_t elektraCryptoGetRandomPasswordLength (Key * errorKey, KeySet * conf)
149 : {
150 1 : Key * k = ksLookupByName (conf, ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD_LEN, 0);
151 1 : if (k && keyIsString (k) > 0)
152 : {
153 0 : kdb_unsigned_short_t passwordLen = (kdb_unsigned_short_t) strtoul (keyString (k), NULL, 10);
154 0 : if (passwordLen > 0)
155 : {
156 : return passwordLen;
157 : }
158 : else
159 : {
160 0 : ELEKTRA_ADD_INSTALLATION_WARNING (errorKey,
161 : "Master password length provided at " ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD_LEN
162 : " is invalid. Using default value instead.");
163 : }
164 : }
165 : return ELEKTRA_CRYPTO_DEFAULT_MASTER_PWD_LENGTH;
166 : }
167 :
168 : /**
169 : * @brief create a random master password using the crypto backend's SRNG.
170 : * @param errorKey holds an error description in case of failure.
171 : * @param buffer is used to store the allocated hex-encoded random string. Must be freed by the caller.
172 : * @param length limit the length of the generated string to length characters (including the 0x00 terminator)
173 : * @retval 1 on success
174 : * @retval -1 on error. errorKey holds a description.
175 : */
176 : static int elektraCryptoCreateRandomString (Key * errorKey ELEKTRA_UNUSED, char ** buffer ELEKTRA_UNUSED,
177 : const kdb_unsigned_short_t length ELEKTRA_UNUSED)
178 : {
179 1 : *buffer = NULL;
180 :
181 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT)
182 1 : *buffer = elektraCryptoGcryCreateRandomString (errorKey, length);
183 : #elif defined(ELEKTRA_CRYPTO_API_OPENSSL)
184 0 : *buffer = elektraCryptoOpenSSLCreateRandomString (errorKey, length);
185 : #elif defined(ELEKTRA_CRYPTO_API_BOTAN)
186 0 : *buffer = elektraCryptoBotanCreateRandomString (errorKey, length);
187 : #endif
188 :
189 1 : if (*buffer) return 1;
190 : return -1;
191 : }
192 :
193 : /**
194 : * @brief overwrites the value of the key with zeroes and then releases the Key.
195 : * @param key to be overwritten and released
196 : */
197 3 : static void elektraCryptoSafelyReleaseKey (Key * key)
198 : {
199 3 : if (key)
200 : {
201 : // overwrite key content with zeroes
202 2 : ssize_t length = keyGetValueSize (key);
203 2 : if (length > 0)
204 : {
205 2 : memset ((void *) keyValue (key), 0, length);
206 : }
207 :
208 : // release the key
209 2 : keyDel (key);
210 : }
211 3 : }
212 :
213 : /**
214 : * @brief encrypt the (Elektra) Keys contained in data.
215 : * @param handle for the current plugin instance
216 : * @param data the KeySet holding the data
217 : * @param errorKey holds an error description in case of failure
218 : * @retval 1 on success
219 : * @retval -1 on failure. errorKey holds an error description.
220 : */
221 2 : static int elektraCryptoEncrypt (Plugin * handle ELEKTRA_UNUSED, KeySet * data ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED)
222 : {
223 : Key * k;
224 2 : Key * masterKey = NULL;
225 :
226 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT) || defined(ELEKTRA_CRYPTO_API_OPENSSL) || defined(ELEKTRA_CRYPTO_API_BOTAN)
227 2 : KeySet * pluginConfig = elektraPluginGetConfig (handle);
228 2 : masterKey = ELEKTRA_PLUGIN_FUNCTION (getMasterPassword) (errorKey, pluginConfig);
229 2 : if (!masterKey)
230 : {
231 : goto error; // error has been set by getMasterPassword
232 : }
233 : #endif
234 :
235 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT) || defined(ELEKTRA_CRYPTO_API_OPENSSL)
236 1 : elektraCryptoHandle * cryptoHandle = NULL;
237 : #endif
238 :
239 1 : ksRewind (data);
240 10 : while ((k = ksNext (data)) != 0)
241 : {
242 14 : if (!isMarkedForEncryption (k) || isSpecNamespace (k))
243 : {
244 2 : continue;
245 : }
246 :
247 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT)
248 :
249 6 : if (elektraCryptoGcryHandleCreate (&cryptoHandle, pluginConfig, errorKey, masterKey, k, ELEKTRA_CRYPTO_ENCRYPT) != 1)
250 : {
251 : goto error;
252 : }
253 :
254 6 : if (elektraCryptoGcryEncrypt (cryptoHandle, k, errorKey) != 1)
255 : {
256 0 : elektraCryptoGcryHandleDestroy (cryptoHandle);
257 0 : goto error;
258 : }
259 :
260 6 : elektraCryptoGcryHandleDestroy (cryptoHandle);
261 6 : cryptoHandle = NULL;
262 :
263 : #elif defined(ELEKTRA_CRYPTO_API_OPENSSL)
264 :
265 0 : if (elektraCryptoOpenSSLHandleCreate (&cryptoHandle, pluginConfig, errorKey, masterKey, k, ELEKTRA_CRYPTO_ENCRYPT) != 1)
266 : {
267 0 : elektraCryptoOpenSSLHandleDestroy (cryptoHandle);
268 0 : goto error;
269 : }
270 :
271 0 : if (elektraCryptoOpenSSLEncrypt (cryptoHandle, k, errorKey) != 1)
272 : {
273 0 : elektraCryptoOpenSSLHandleDestroy (cryptoHandle);
274 0 : goto error;
275 : }
276 :
277 0 : elektraCryptoOpenSSLHandleDestroy (cryptoHandle);
278 0 : cryptoHandle = NULL;
279 :
280 : #elif defined(ELEKTRA_CRYPTO_API_BOTAN)
281 :
282 0 : if (elektraCryptoBotanEncrypt (pluginConfig, k, errorKey, masterKey) != 1)
283 : {
284 : goto error; // failure, error has been set by elektraCryptoBotanEncrypt
285 : }
286 :
287 : #endif
288 : }
289 1 : elektraCryptoSafelyReleaseKey (masterKey);
290 1 : return 1;
291 :
292 : error:
293 1 : elektraCryptoSafelyReleaseKey (masterKey);
294 1 : return -1;
295 : }
296 :
297 : /**
298 : * @brief decrypt the (Elektra) Keys contained in data.
299 : * @param handle for the current plugin instance
300 : * @param data the KeySet holding the data
301 : * @param errorKey holds an error description in case of failure
302 : * @retval 1 on success
303 : * @retval -1 on failure. errorKey holds an error description.
304 : */
305 1 : static int elektraCryptoDecrypt (Plugin * handle ELEKTRA_UNUSED, KeySet * data, Key * errorKey)
306 : {
307 : Key * k;
308 1 : Key * masterKey = NULL;
309 :
310 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT) || defined(ELEKTRA_CRYPTO_API_OPENSSL) || defined(ELEKTRA_CRYPTO_API_BOTAN)
311 1 : KeySet * pluginConfig = elektraPluginGetConfig (handle);
312 1 : masterKey = ELEKTRA_PLUGIN_FUNCTION (getMasterPassword) (errorKey, pluginConfig);
313 1 : if (!masterKey)
314 : {
315 : goto error; // error has been set by getMasterPassword
316 : }
317 : #endif
318 :
319 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT) || defined(ELEKTRA_CRYPTO_API_OPENSSL)
320 1 : elektraCryptoHandle * cryptoHandle = NULL;
321 : #endif
322 :
323 1 : ksRewind (data);
324 10 : while ((k = ksNext (data)) != 0)
325 : {
326 14 : if (!isMarkedForEncryption (k) || isSpecNamespace (k))
327 : {
328 2 : continue;
329 : }
330 :
331 6 : if (!checkPayloadVersion (k, errorKey))
332 : {
333 : // error has been set by checkPayloadVersion()
334 : goto error;
335 : }
336 :
337 : #if defined(ELEKTRA_CRYPTO_API_GCRYPT)
338 :
339 6 : if (elektraCryptoGcryHandleCreate (&cryptoHandle, pluginConfig, errorKey, masterKey, k, ELEKTRA_CRYPTO_DECRYPT) != 1)
340 : {
341 : goto error;
342 : }
343 :
344 6 : if (elektraCryptoGcryDecrypt (cryptoHandle, k, errorKey) != 1)
345 : {
346 0 : elektraCryptoGcryHandleDestroy (cryptoHandle);
347 0 : goto error;
348 : }
349 :
350 6 : elektraCryptoGcryHandleDestroy (cryptoHandle);
351 6 : cryptoHandle = NULL;
352 :
353 : #elif defined(ELEKTRA_CRYPTO_API_OPENSSL)
354 :
355 0 : if (elektraCryptoOpenSSLHandleCreate (&cryptoHandle, pluginConfig, errorKey, masterKey, k, ELEKTRA_CRYPTO_DECRYPT) != 1)
356 : {
357 0 : elektraCryptoOpenSSLHandleDestroy (cryptoHandle);
358 0 : goto error;
359 : }
360 :
361 0 : if (elektraCryptoOpenSSLDecrypt (cryptoHandle, k, errorKey) != 1)
362 : {
363 0 : elektraCryptoOpenSSLHandleDestroy (cryptoHandle);
364 0 : goto error;
365 : }
366 :
367 0 : elektraCryptoOpenSSLHandleDestroy (cryptoHandle);
368 0 : cryptoHandle = NULL;
369 :
370 : #elif defined(ELEKTRA_CRYPTO_API_BOTAN)
371 :
372 0 : if (elektraCryptoBotanDecrypt (pluginConfig, k, errorKey, masterKey) != 1)
373 : {
374 : goto error; // failure, error has been set by elektraCryptoBotanDecrypt
375 : }
376 :
377 : #endif
378 : }
379 1 : elektraCryptoSafelyReleaseKey (masterKey);
380 1 : return 1;
381 :
382 : error:
383 0 : elektraCryptoSafelyReleaseKey (masterKey);
384 0 : return -1;
385 : }
386 :
387 : /**
388 : * @brief initialize the crypto provider for the first instance of the plugin.
389 : *
390 : * @param handle holds the plugin handle
391 : * @param errorKey holds an error description in case of failure
392 : * @retval 1 on success
393 : * @retval -1 on failure. Check errorKey
394 : */
395 64 : int ELEKTRA_PLUGIN_FUNCTION (open) (Plugin * handle ELEKTRA_UNUSED, Key * errorKey)
396 : {
397 64 : pthread_mutex_lock (&mutex_ref_cnt);
398 64 : if (ref_cnt == 0)
399 : {
400 49 : if (elektraCryptoInit (errorKey) != 1)
401 : {
402 0 : pthread_mutex_unlock (&mutex_ref_cnt);
403 0 : return -1;
404 : }
405 : }
406 64 : ref_cnt++;
407 64 : pthread_mutex_unlock (&mutex_ref_cnt);
408 64 : return 1;
409 : }
410 :
411 : /**
412 : * @brief finalizes the crypto provider for the last instance of the plugin.
413 : *
414 : * @param handle holds the plugin handle
415 : * @param errorKey holds an error description in case of failure. Not used at the moment.
416 : * @retval 1 on success
417 : * @retval -1 on failure
418 : */
419 65 : int ELEKTRA_PLUGIN_FUNCTION (close) (Plugin * handle, Key * errorKey ELEKTRA_UNUSED)
420 : {
421 : /* default behaviour: no teardown except the user/system requests it */
422 65 : KeySet * pluginConfig = elektraPluginGetConfig (handle);
423 65 : if (!pluginConfig)
424 : {
425 : return -1; // failure because of missing plugin config
426 : }
427 :
428 65 : Key * shutdown = ksLookupByName (pluginConfig, ELEKTRA_CRYPTO_PARAM_SHUTDOWN, 0);
429 65 : if (!shutdown)
430 : {
431 : return 1; // applying default behaviour -> success
432 : }
433 : else
434 : {
435 1 : if (strcmp (keyString (shutdown), "1") != 0)
436 : {
437 : return 1; // applying default behaviour -> success
438 : }
439 : }
440 :
441 1 : pthread_mutex_lock (&mutex_ref_cnt);
442 1 : if (--ref_cnt == 0)
443 : {
444 : elektraCryptoTeardown ();
445 : }
446 1 : pthread_mutex_unlock (&mutex_ref_cnt);
447 1 : return 1; // success
448 : }
449 :
450 : /**
451 : * @brief establish the Elektra plugin contract and decrypt values, if possible.
452 : *
453 : * The crypto configuration is expected to be contained within the KeySet ks.
454 : * All keys having a metakey "crypto/encrypted" with a strlen() > 0 are being decrypted.
455 : *
456 : * @param handle holds the plugin handle
457 : * @param ks holds the data to be operated on
458 : * @param parentKey holds an error description in case of failure
459 : * @retval 1 on success
460 : * @retval -1 on failure. Check parentKey.
461 : */
462 62 : int ELEKTRA_PLUGIN_FUNCTION (get) (Plugin * handle, KeySet * ks, Key * parentKey)
463 : {
464 : // Publish module configuration to Elektra (establish the contract)
465 62 : if (!strcmp (keyName (parentKey), "system/elektra/modules/" ELEKTRA_PLUGIN_NAME))
466 : {
467 61 : KeySet * moduleConfig = ksNew (30,
468 : #include "contract.h"
469 : KS_END);
470 61 : ksAppend (ks, moduleConfig);
471 61 : ksDel (moduleConfig);
472 61 : return 1;
473 : }
474 :
475 1 : return elektraCryptoDecrypt (handle, ks, parentKey);
476 : }
477 :
478 : /**
479 : * @brief Encrypt values marked for encryption.
480 : *
481 : * If a key has the metakey "crypto/encrypt" with a strlen() > 0, then the value
482 : * will be encrypted using the configuration stored in the KeySet ks.
483 : *
484 : * @param handle holds the plugin handle
485 : * @param ks holds the data to be operated on
486 : * @param parentKey holds an error description in case of failure
487 : * @retval 1 on success
488 : * @retval -1 on failure. Check parentKey.
489 : */
490 2 : int ELEKTRA_PLUGIN_FUNCTION (set) (Plugin * handle, KeySet * ks, Key * parentKey)
491 : {
492 2 : return elektraCryptoEncrypt (handle, ks, parentKey);
493 : }
494 :
495 : /**
496 : * @brief Checks for the existence of the master password, that is used for encryption and decryption.
497 : *
498 : * If the master password can not be found it will be generated randomly.
499 : * Then it will be encrypted and stored in conf.
500 : *
501 : * If the master password can be found, it will be decrypted temporarily in order to verify its correctness.
502 : * conf will not be modified in this case.
503 : *
504 : * An error might occur during the password generation, encryption and decryption.
505 : * The error will be appended to errorKey.
506 : *
507 : * @param errorKey holds an error description in case of failure
508 : * @param conf holds the plugin configuration
509 : * @retval 0 no changes were made to the configuration
510 : * @retval 1 the master password has been appended to the configuration
511 : * @retval -1 an error occurred. Check errorKey
512 : */
513 1 : int ELEKTRA_PLUGIN_FUNCTION (checkconf) (Key * errorKey, KeySet * conf)
514 : {
515 1 : Key * k = ksLookupByName (conf, ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD, 0);
516 1 : if (k)
517 : {
518 : // call gpg module to verify that we own the required key
519 0 : Key * msg = keyDup (k);
520 0 : if (ELEKTRA_PLUGIN_FUNCTION (gpgDecryptMasterPassword) (conf, errorKey, msg) != 1)
521 : {
522 0 : keyDel (msg);
523 0 : return -1; // error set by ELEKTRA_PLUGIN_FUNCTION(gpgDecryptMasterPassword)()
524 : }
525 0 : keyDel (msg);
526 0 : return 0;
527 : }
528 : else
529 : {
530 : // generate random master password
531 1 : const kdb_unsigned_short_t passwordLen = elektraCryptoGetRandomPasswordLength (errorKey, conf);
532 1 : char * r = NULL;
533 2 : if (elektraCryptoCreateRandomString (errorKey, &r, passwordLen) != 1)
534 : {
535 : return -1; // error set by elektraCryptoCreateRandomString()
536 : }
537 :
538 : // store password in configuration
539 1 : k = keyNew ("user/" ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD, KEY_END);
540 1 : keySetString (k, r);
541 1 : elektraFree (r);
542 1 : if (ELEKTRA_PLUGIN_FUNCTION (gpgEncryptMasterPassword) (conf, errorKey, k) != 1)
543 : {
544 0 : keyDel (k);
545 0 : return -1; // error set by ELEKTRA_PLUGIN_FUNCTION(gpgEncryptMasterPassword)()
546 : }
547 1 : ksAppendKey (conf, k);
548 1 : return 1;
549 : }
550 : }
551 :
552 63 : Plugin * ELEKTRA_PLUGIN_EXPORT
553 : {
554 : // clang-format off
555 63 : return elektraPluginExport(ELEKTRA_PLUGIN_NAME,
556 : ELEKTRA_PLUGIN_OPEN, &ELEKTRA_PLUGIN_FUNCTION(open),
557 : ELEKTRA_PLUGIN_CLOSE, &ELEKTRA_PLUGIN_FUNCTION(close),
558 : ELEKTRA_PLUGIN_GET, &ELEKTRA_PLUGIN_FUNCTION(get),
559 : ELEKTRA_PLUGIN_SET, &ELEKTRA_PLUGIN_FUNCTION(set),
560 : ELEKTRA_PLUGIN_END);
561 : }
|