LCOV - code coverage report
Current view: top level - src/plugins/crypto - crypto.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 102 128 79.7 %
Date: 2022-05-21 16:19:22 Functions: 12 13 92.3 %

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

Generated by: LCOV version 1.13