LCOV - code coverage report
Current view: top level - src/plugins/crypto - helper.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 106 131 80.9 %
Date: 2022-05-21 16:19:22 Functions: 9 9 100.0 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief helper functions for the crypto plugin
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  *
       8             :  */
       9             : 
      10             : #include "helper.h"
      11             : #include "crypto.h"
      12             : #include "gpg.h"
      13             : #include <kdbassert.h>
      14             : #include <kdberrors.h>
      15             : #include <kdbinvoke.h>
      16             : #include <stdlib.h>
      17             : 
      18             : /**
      19             :  * @brief lookup if the test mode for unit testing is enabled.
      20             :  * @param conf KeySet holding the plugin configuration.
      21             :  * @retval 0 test mode is not enabled
      22             :  * @retval 1 test mode is enabled
      23             :  */
      24          19 : static int inTestMode (KeySet * conf)
      25             : {
      26          19 :         Key * k = ksLookupByName (conf, ELEKTRA_CRYPTO_PARAM_GPG_UNIT_TEST, 0);
      27          19 :         if (k && !strcmp (keyString (k), "1"))
      28             :         {
      29           6 :                 return 1;
      30             :         }
      31             :         return 0;
      32             : }
      33             : 
      34             : /**
      35             :  * @brief Encodes arbitrary data using the Base64 schema by utilizing libinvoke.
      36             :  * @param errorKey will hold an error description if libinvoke fails.
      37             :  * @param input holds the data to be encoded
      38             :  * @param inputLength tells how many bytes the input buffer is holding.
      39             :  * @param output points to an allocated string holding the Base64 encoded input data or NULL if the string can not be allocated. Must be
      40             :  * freed by the caller.
      41             :  * @retval 1 on success
      42             :  * @retval -1 if libinvoke reported an error (errorKey is being set).
      43             :  */
      44          21 : int ELEKTRA_PLUGIN_FUNCTION (base64Encode) (Key * errorKey, const kdb_octet_t * input, const size_t inputLength, char ** output)
      45             : {
      46          21 :         ElektraInvokeHandle * handle = elektraInvokeOpen ("base64", 0, errorKey);
      47          21 :         if (!handle)
      48             :         {
      49             :                 return -1;
      50             :         }
      51             : 
      52          21 :         typedef char * (*base64EncodeFunction) (const kdb_octet_t * input, const size_t inputLength);
      53          21 :         base64EncodeFunction encodingFunction = *(base64EncodeFunction *) elektraInvokeGetFunction (handle, "base64Encode");
      54             : 
      55          21 :         if (!encodingFunction)
      56             :         {
      57           0 :                 elektraInvokeClose (handle, 0);
      58           0 :                 return -1;
      59             :         }
      60             : 
      61          21 :         *output = encodingFunction (input, inputLength);
      62          21 :         elektraInvokeClose (handle, 0);
      63          21 :         return 1;
      64             : }
      65             : 
      66             : /**
      67             :  * @brief decodes Base64 encoded data by utilizing libinvoke.
      68             :  * @param input holds the Base64 encoded data string
      69             :  * @param output will be set to an allocated buffer holding the decoded data or NULL if the allocation failed. Must be freed by the caller
      70             :           on success.
      71             :  * @param outputLength will be set to the amount of decoded bytes.
      72             :  * @param errorKey will hold an error description if libinvoke fails.
      73             :  * @retval 1 on success
      74             :  * @retval -1 if the provided string has not been encoded with Base64
      75             :  * @retval -2 if the output buffer allocation failed
      76             :  * @retval -3 if libinvoke reported an error (errorKey is being set).
      77             :  */
      78          31 : int ELEKTRA_PLUGIN_FUNCTION (base64Decode) (Key * errorKey, const char * input, kdb_octet_t ** output, size_t * outputLength)
      79             : {
      80          31 :         ElektraInvokeHandle * handle = elektraInvokeOpen ("base64", 0, errorKey);
      81          31 :         if (!handle)
      82             :         {
      83             :                 return -3;
      84             :         }
      85             : 
      86          31 :         typedef int (*base64DecodeFunction) (const char * input, kdb_octet_t ** output, size_t * outputLength);
      87          31 :         base64DecodeFunction decodingFunction = *(base64DecodeFunction *) elektraInvokeGetFunction (handle, "base64Decode");
      88             : 
      89          31 :         if (!decodingFunction)
      90             :         {
      91           0 :                 elektraInvokeClose (handle, 0);
      92           0 :                 return -3;
      93             :         }
      94             : 
      95          31 :         int result = decodingFunction (input, output, outputLength);
      96          31 :         elektraInvokeClose (handle, 0);
      97          31 :         return result;
      98             : }
      99             : 
     100             : /**
     101             :  * @brief parse the hex-encoded salt from the metakey.
     102             :  * @param errorKey holds an error description in case of failure.
     103             :  * @param k holds the salt as metakey
     104             :  * @param salt is set to an allocated buffer containing the salt. Must be freed by the caller.
     105             :  * @param saltLen is set to the length of the salt. Ignored if NULL is provided.
     106             :  * @retval 1 on success
     107             :  * @retval -1 on error. errorKey holds a description.
     108             :  */
     109          15 : int ELEKTRA_PLUGIN_FUNCTION (getSaltFromMetakey) (Key * errorKey, Key * k, kdb_octet_t ** salt, kdb_unsigned_long_t * saltLen)
     110             : {
     111          15 :         size_t saltLenInternal = 0;
     112          15 :         const Key * meta = keyGetMeta (k, ELEKTRA_CRYPTO_META_SALT);
     113          15 :         if (!meta)
     114             :         {
     115           0 :                 ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (errorKey, "Missing salt as metakey %s in key %s", ELEKTRA_CRYPTO_META_SALT,
     116             :                                                         keyName (k));
     117           0 :                 return -1;
     118             :         }
     119             : 
     120          15 :         int result = ELEKTRA_PLUGIN_FUNCTION (base64Decode) (errorKey, keyString (meta), salt, &saltLenInternal);
     121          15 :         if (result == -1)
     122             :         {
     123           0 :                 ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (errorKey, "Salt was not stored Base64 encoded in key %s", keyName (k));
     124           0 :                 return -1;
     125             :         }
     126          15 :         else if (result == -2)
     127             :         {
     128           0 :                 ELEKTRA_SET_OUT_OF_MEMORY_ERROR (errorKey);
     129           0 :                 return -1;
     130             :         }
     131          15 :         else if (result < -2)
     132             :         {
     133             :                 // errorKey has been set by base64Decode (...)
     134             :                 return -1;
     135             :         }
     136             : 
     137          15 :         *saltLen = saltLenInternal;
     138          15 :         return 1;
     139             : }
     140             : 
     141             : /**
     142             :  * @brief parse the salt from the crypto payload in the given (Elektra) Key.
     143             :  * @param errorKey holds an error description in case of failure.
     144             :  * @param k holds the crypto paylaod.
     145             :  * @param salt is set to the location of the salt within the crypto payload. Ignored if NULL is provided.
     146             :  * @param saltLen is set to the length of the salt. Ignored if NULL is provided.
     147             :  * @retval 1 on success
     148             :  * @retval -1 on error. errorKey holds a description.
     149             :  */
     150          32 : int ELEKTRA_PLUGIN_FUNCTION (getSaltFromPayload) (Key * errorKey, Key * k, kdb_octet_t ** salt, kdb_unsigned_long_t * saltLen)
     151             : {
     152          32 :         static const size_t headerLen = sizeof (kdb_unsigned_long_t);
     153          32 :         const ssize_t payloadLen = keyGetValueSize (k) - ELEKTRA_CRYPTO_MAGIC_NUMBER_LEN;
     154             : 
     155             :         // validate payload length
     156          32 :         if ((size_t) payloadLen < sizeof (size_t) || payloadLen < 0)
     157             :         {
     158           0 :                 ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (errorKey, "Payload is too small to contain a salt (payload length is: %zu)",
     159             :                                                         payloadLen);
     160           0 :                 if (salt) *salt = NULL;
     161           0 :                 return -1;
     162             :         }
     163             : 
     164          32 :         const kdb_octet_t * value = (kdb_octet_t *) keyValue (k);
     165          32 :         const kdb_octet_t * payload = &value[ELEKTRA_CRYPTO_MAGIC_NUMBER_LEN];
     166          32 :         kdb_unsigned_long_t restoredSaltLen = 0;
     167             : 
     168             :         // restore salt length
     169          32 :         memcpy (&restoredSaltLen, payload, headerLen);
     170          32 :         if (saltLen) *saltLen = restoredSaltLen;
     171             : 
     172             :         // validate restored salt length
     173          32 :         if (restoredSaltLen < 1 || restoredSaltLen > (payloadLen - headerLen))
     174             :         {
     175           0 :                 ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (errorKey, "Restored salt has invalid length of %u (payload length is: %zu)",
     176             :                                                         restoredSaltLen, payloadLen);
     177           0 :                 if (salt) *salt = NULL;
     178           0 :                 return -1;
     179             :         }
     180             : 
     181             :         // restore salt
     182          32 :         if (salt) *salt = ((kdb_octet_t *) (payload)) + headerLen;
     183             : 
     184             :         return 1;
     185             : }
     186             : 
     187             : /**
     188             :  * @brief read the encrypted password form the configuration and decrypt it.
     189             :  * @param errorKey holds an error description in case of failure.
     190             :  * @param config holds the plugin configuration.
     191             :  * @returns the decrypted master password as (Elektra) Key or NULL in case of error. Must be freed by the caller.
     192             :  */
     193          18 : Key * ELEKTRA_PLUGIN_FUNCTION (getMasterPassword) (Key * errorKey, KeySet * config)
     194             : {
     195          18 :         Key * master = ksLookupByName (config, ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD, 0);
     196          18 :         if (!master)
     197             :         {
     198           2 :                 ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Missing %s in plugin configuration", ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD);
     199           2 :                 return NULL;
     200             :         }
     201          16 :         Key * msg = keyDup (master, KEY_CP_ALL);
     202          16 :         if (ELEKTRA_PLUGIN_FUNCTION (gpgDecryptMasterPassword) (config, errorKey, msg) != 1)
     203             :         {
     204           0 :                 keyDel (msg);
     205           0 :                 return NULL; // error set by ELEKTRA_PLUGIN_FUNCTION(gpgDecryptMasterPassword)()
     206             :         }
     207             :         return msg;
     208             : }
     209             : 
     210             : /**
     211             :  * @brief read the desired iteration count from config
     212             :  * @param errorKey may hold a warning if an invalid configuration is provided
     213             :  * @param config KeySet holding the plugin configuration
     214             :  * @returns the number of iterations for the key derivation function
     215             :  */
     216          31 : kdb_unsigned_long_t ELEKTRA_PLUGIN_FUNCTION (getIterationCount) (Key * errorKey, KeySet * config)
     217             : {
     218          31 :         Key * k = ksLookupByName (config, ELEKTRA_CRYPTO_PARAM_ITERATION_COUNT, 0);
     219          31 :         if (k)
     220             :         {
     221           0 :                 const kdb_unsigned_long_t iterations = strtoul (keyString (k), NULL, 10);
     222           0 :                 if (iterations > 0)
     223             :                 {
     224             :                         return iterations;
     225             :                 }
     226             :                 else
     227             :                 {
     228           0 :                         ELEKTRA_ADD_INSTALLATION_WARNING (errorKey, "Iteration count provided at " ELEKTRA_CRYPTO_PARAM_ITERATION_COUNT
     229             :                                                                     " is invalid. Using default value instead.");
     230             :                 }
     231             :         }
     232             :         return ELEKTRA_CRYPTO_DEFAULT_ITERATION_COUNT;
     233             : }
     234             : 
     235             : /**
     236             :  * @brief call the gpg binary to encrypt the random master password.
     237             :  *
     238             :  * @param conf holds the backend/plugin configuration
     239             :  * @param errorKey holds the error description in case of failure
     240             :  * @param msgKey holds the master password to be encrypted
     241             :  *
     242             :  * @retval 1 on success
     243             :  * @retval -1 on failure
     244             :  */
     245           3 : int ELEKTRA_PLUGIN_FUNCTION (gpgEncryptMasterPassword) (KeySet * conf, Key * errorKey, Key * msgKey)
     246             : {
     247             :         // [0]: <path to binary>, [argc-3]: --batch, [argc-2]: -e, [argc-1]: NULL-terminator
     248           3 :         static const kdb_unsigned_short_t staticArgumentsCount = 4;
     249           3 :         Key * k;
     250             : 
     251             :         // determine the number of total GPG keys to be used
     252           3 :         kdb_unsigned_short_t recipientCount = 0;
     253           3 :         kdb_unsigned_short_t testMode = 0;
     254           3 :         Key * root = ksLookupByName (conf, ELEKTRA_RECIPIENT_KEY, 0);
     255             : 
     256             :         // check root key crypto/key
     257           3 :         if (root && strlen (keyString (root)) > 0)
     258             :         {
     259           2 :                 recipientCount++;
     260             :         }
     261             : 
     262             :         // check for key beneath crypto/key (like crypto/key/#0 etc)
     263           3 :         ksRewind (conf);
     264          13 :         while ((k = ksNext (conf)) != 0)
     265             :         {
     266           7 :                 if (keyIsBelow (k, root) && strlen (keyString (k)) > 0)
     267             :                 {
     268           1 :                         recipientCount++;
     269             :                 }
     270             :         }
     271             : 
     272           3 :         if (recipientCount == 0)
     273             :         {
     274           0 :                 char * errorDescription = ELEKTRA_PLUGIN_FUNCTION (getMissingGpgKeyErrorText) (conf);
     275           0 :                 ELEKTRA_SET_VALIDATION_SEMANTIC_ERROR (errorKey, errorDescription);
     276           0 :                 elektraFree (errorDescription);
     277           0 :                 return -1;
     278             :         }
     279             : 
     280           3 :         if (inTestMode (conf))
     281             :         {
     282             :                 // add two parameters for unit testing
     283           2 :                 testMode = 2;
     284             :         }
     285             : 
     286             :         // initialize argument vector for gpg call
     287           3 :         const kdb_unsigned_short_t argc = (2 * recipientCount) + staticArgumentsCount + testMode;
     288           3 :         kdb_unsigned_short_t i = 1;
     289           3 :         char * argv[argc];
     290             : 
     291             :         // append root (crypto/key) as gpg recipient
     292           3 :         if (root && strlen (keyString (root)) > 0)
     293             :         {
     294           2 :                 argv[i++] = "-r";
     295             :                 // NOTE argv[] values will not be modified, so const can be discarded safely
     296           2 :                 argv[i++] = (char *) keyString (root);
     297             :         }
     298             : 
     299             :         // append keys beneath root (crypto/key/#_) as gpg recipients
     300           3 :         ksRewind (conf);
     301          10 :         while ((k = ksNext (conf)) != 0)
     302             :         {
     303           7 :                 const char * kStringVal = keyString (k);
     304           7 :                 if (keyIsBelow (k, root) && strlen (kStringVal) > 0)
     305             :                 {
     306           1 :                         argv[i++] = "-r";
     307             :                         // NOTE argv[] values will not be modified, so const can be discarded safely
     308           1 :                         argv[i++] = (char *) kStringVal;
     309             :                 }
     310             :         }
     311             : 
     312             :         // append option for unit tests
     313           3 :         if (testMode)
     314             :         {
     315           2 :                 argv[i++] = "--trust-model";
     316           2 :                 argv[i++] = "always";
     317             :         }
     318             : 
     319           3 :         argv[i++] = "--batch";
     320           3 :         argv[i++] = "-e";
     321           3 :         argv[i++] = NULL;
     322             : 
     323           3 :         ELEKTRA_ASSERT (i == argc, "invalid number of arguments generated in method gpgEncryptMasterPassword()");
     324             : 
     325             :         // call gpg
     326           3 :         int gpgResult = ELEKTRA_PLUGIN_FUNCTION (gpgCall) (conf, errorKey, msgKey, argv, argc);
     327           3 :         if (gpgResult != 1) // no success
     328             :         {
     329             :                 return gpgResult; // error set by ELEKTRA_PLUGIN_FUNCTION (gpgCall)
     330             :         }
     331             : 
     332             :         // encode result as Base64 string
     333           3 :         char * base64Encoded = NULL;
     334           3 :         int base64Result = ELEKTRA_PLUGIN_FUNCTION (base64Encode) (errorKey, keyValue (msgKey), keyGetValueSize (msgKey), &base64Encoded);
     335           3 :         if (base64Encoded)
     336             :         {
     337           3 :                 keySetString (msgKey, base64Encoded);
     338           3 :                 elektraFree (base64Encoded);
     339             :         }
     340             : 
     341             :         return base64Result;
     342             : }
     343             : 
     344             : /**
     345             :  * @brief call the gpg binary to decrypt the random master password.
     346             :  *
     347             :  * @param conf holds the backend/plugin configuration
     348             :  * @param errorKey holds the error description in case of failure
     349             :  * @param msgKey holds the master password to be decrypted. Note that the content of this key will be modified.
     350             :  *
     351             :  * @retval 1 on success
     352             :  * @retval -1 on failure
     353             :  */
     354          16 : int ELEKTRA_PLUGIN_FUNCTION (gpgDecryptMasterPassword) (KeySet * conf, Key * errorKey, Key * msgKey)
     355             : {
     356          16 :         kdb_octet_t * binaryData = NULL;
     357          16 :         size_t binaryDataLength;
     358             : 
     359             :         // decode the master password string
     360          16 :         ELEKTRA_PLUGIN_FUNCTION (base64Decode) (errorKey, keyString (msgKey), &binaryData, &binaryDataLength);
     361          16 :         if (binaryData)
     362             :         {
     363          16 :                 keySetBinary (msgKey, binaryData, binaryDataLength);
     364          16 :                 elektraFree (binaryData);
     365             :         }
     366             : 
     367             :         // password decryption
     368          16 :         if (inTestMode (conf))
     369             :         {
     370           4 :                 char * argv[] = { "", "--batch", "--trust-model", "always", "-d", NULL };
     371           4 :                 return ELEKTRA_PLUGIN_FUNCTION (gpgCall) (conf, errorKey, msgKey, argv, 6);
     372             :         }
     373             :         else
     374             :         {
     375          12 :                 char * argv[] = { "", "--batch", "-d", NULL };
     376          12 :                 return ELEKTRA_PLUGIN_FUNCTION (gpgCall) (conf, errorKey, msgKey, argv, 4);
     377             :         }
     378             : }

Generated by: LCOV version 1.13