LCOV - code coverage report
Current view: top level - src/plugins/crypto - helper.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 94 122 77.0 %
Date: 2019-09-12 12:28:41 Functions: 9 25 36.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           3 : static int inTestMode (KeySet * conf)
      25             : {
      26           3 :         Key * k = ksLookupByName (conf, ELEKTRA_CRYPTO_PARAM_GPG_UNIT_TEST, 0);
      27           3 :         if (k && !strcmp (keyString (k), "1"))
      28             :         {
      29             :                 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           8 : int ELEKTRA_PLUGIN_FUNCTION (base64Encode) (Key * errorKey, const kdb_octet_t * input, const size_t inputLength, char ** output)
      45             : {
      46           8 :         ElektraInvokeHandle * handle = elektraInvokeOpen ("base64", 0, errorKey);
      47           8 :         if (!handle)
      48             :         {
      49             :                 return -1;
      50             :         }
      51             : 
      52             :         typedef char * (*base64EncodeFunction) (const kdb_octet_t * input, const size_t inputLength);
      53           8 :         base64EncodeFunction encodingFunction = *(base64EncodeFunction *) elektraInvokeGetFunction (handle, "base64Encode");
      54             : 
      55           8 :         if (!encodingFunction)
      56             :         {
      57           0 :                 elektraInvokeClose (handle, 0);
      58           0 :                 return -1;
      59             :         }
      60             : 
      61           8 :         *output = encodingFunction (input, inputLength);
      62           8 :         elektraInvokeClose (handle, 0);
      63           8 :         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           8 : int ELEKTRA_PLUGIN_FUNCTION (base64Decode) (Key * errorKey, const char * input, kdb_octet_t ** output, size_t * outputLength)
      79             : {
      80           8 :         ElektraInvokeHandle * handle = elektraInvokeOpen ("base64", 0, errorKey);
      81           8 :         if (!handle)
      82             :         {
      83             :                 return -3;
      84             :         }
      85             : 
      86             :         typedef int (*base64DecodeFunction) (const char * input, kdb_octet_t ** output, size_t * outputLength);
      87           8 :         base64DecodeFunction decodingFunction = *(base64DecodeFunction *) elektraInvokeGetFunction (handle, "base64Decode");
      88             : 
      89           8 :         if (!decodingFunction)
      90             :         {
      91           0 :                 elektraInvokeClose (handle, 0);
      92           0 :                 return -3;
      93             :         }
      94             : 
      95           8 :         int result = decodingFunction (input, output, outputLength);
      96           8 :         elektraInvokeClose (handle, 0);
      97           8 :         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           6 : int ELEKTRA_PLUGIN_FUNCTION (getSaltFromMetakey) (Key * errorKey, Key * k, kdb_octet_t ** salt, kdb_unsigned_long_t * saltLen)
     110             : {
     111           6 :         size_t saltLenInternal = 0;
     112           6 :         const Key * meta = keyGetMeta (k, ELEKTRA_CRYPTO_META_SALT);
     113           6 :         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           6 :         int result = ELEKTRA_PLUGIN_FUNCTION (base64Decode) (errorKey, keyString (meta), salt, &saltLenInternal);
     121           6 :         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           6 :         else if (result == -2)
     127             :         {
     128           0 :                 ELEKTRA_SET_OUT_OF_MEMORY_ERROR (errorKey, "Memory allocation failed");
     129           0 :                 return -1;
     130             :         }
     131           6 :         else if (result < -2)
     132             :         {
     133             :                 // errorKey has been set by base64Decode (...)
     134             :                 return -1;
     135             :         }
     136             : 
     137           6 :         *saltLen = saltLenInternal;
     138           6 :         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          12 : int ELEKTRA_PLUGIN_FUNCTION (getSaltFromPayload) (Key * errorKey, Key * k, kdb_octet_t ** salt, kdb_unsigned_long_t * saltLen)
     151             : {
     152             :         static const size_t headerLen = sizeof (kdb_unsigned_long_t);
     153          12 :         const ssize_t payloadLen = keyGetValueSize (k) - ELEKTRA_CRYPTO_MAGIC_NUMBER_LEN;
     154             : 
     155             :         // validate payload length
     156          12 :         if ((size_t) payloadLen < sizeof (size_t) || payloadLen < 0)
     157             :         {
     158             :                 // TODO: Correct??
     159           0 :                 ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Payload is too small to contain a salt (payload length is: %zu)", payloadLen);
     160           0 :                 if (salt) *salt = NULL;
     161             :                 return -1;
     162             :         }
     163             : 
     164          12 :         const kdb_octet_t * value = (kdb_octet_t *) keyValue (k);
     165          12 :         const kdb_octet_t * payload = &value[ELEKTRA_CRYPTO_MAGIC_NUMBER_LEN];
     166          12 :         kdb_unsigned_long_t restoredSaltLen = 0;
     167             : 
     168             :         // restore salt length
     169          12 :         memcpy (&restoredSaltLen, payload, headerLen);
     170          12 :         if (saltLen) *saltLen = restoredSaltLen;
     171             : 
     172             :         // validate restored salt length
     173          12 :         if (restoredSaltLen < 1 || restoredSaltLen > (payloadLen - headerLen))
     174             :         {
     175             :                 // TODO: Correct??
     176           0 :                 ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Restored salt has invalid length of %u (payload length is: %zu)", restoredSaltLen,
     177             :                                              payloadLen);
     178           0 :                 if (salt) *salt = NULL;
     179             :                 return -1;
     180             :         }
     181             : 
     182             :         // restore salt
     183          12 :         if (salt) *salt = ((kdb_octet_t *) (payload)) + headerLen;
     184             : 
     185             :         return 1;
     186             : }
     187             : 
     188             : /**
     189             :  * @brief read the encrypted password form the configuration and decrypt it.
     190             :  * @param errorKey holds an error description in case of failure.
     191             :  * @param config holds the plugin configuration.
     192             :  * @returns the decrypted master password as (Elektra) Key or NULL in case of error. Must be freed by the caller.
     193             :  */
     194           3 : Key * ELEKTRA_PLUGIN_FUNCTION (getMasterPassword) (Key * errorKey, KeySet * config)
     195             : {
     196           3 :         Key * master = ksLookupByName (config, ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD, 0);
     197           3 :         if (!master)
     198             :         {
     199           1 :                 ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Missing %s in plugin configuration", ELEKTRA_CRYPTO_PARAM_MASTER_PASSWORD);
     200           1 :                 return NULL;
     201             :         }
     202           2 :         Key * msg = keyDup (master);
     203           2 :         if (ELEKTRA_PLUGIN_FUNCTION (gpgDecryptMasterPassword) (config, errorKey, msg) != 1)
     204             :         {
     205           0 :                 keyDel (msg);
     206           0 :                 return NULL; // error set by ELEKTRA_PLUGIN_FUNCTION(gpgDecryptMasterPassword)()
     207             :         }
     208             :         return msg;
     209             : }
     210             : 
     211             : /**
     212             :  * @brief read the desired iteration count from config
     213             :  * @param errorKey may hold a warning if an invalid configuration is provided
     214             :  * @param config KeySet holding the plugin configuration
     215             :  * @returns the number of iterations for the key derivation function
     216             :  */
     217          12 : kdb_unsigned_long_t ELEKTRA_PLUGIN_FUNCTION (getIterationCount) (Key * errorKey, KeySet * config)
     218             : {
     219          12 :         Key * k = ksLookupByName (config, ELEKTRA_CRYPTO_PARAM_ITERATION_COUNT, 0);
     220          12 :         if (k)
     221             :         {
     222           0 :                 const kdb_unsigned_long_t iterations = strtoul (keyString (k), NULL, 10);
     223           0 :                 if (iterations > 0)
     224             :                 {
     225             :                         return iterations;
     226             :                 }
     227             :                 else
     228             :                 {
     229             :                         // TODO: Correct?
     230           0 :                         ELEKTRA_ADD_INSTALLATION_WARNING (errorKey, "Iteration count provided at " ELEKTRA_CRYPTO_PARAM_ITERATION_COUNT
     231             :                                                                     " is invalid. Using default value instead.");
     232             :                 }
     233             :         }
     234             :         return ELEKTRA_CRYPTO_DEFAULT_ITERATION_COUNT;
     235             : }
     236             : 
     237             : /**
     238             :  * @brief call the gpg binary to encrypt the random master password.
     239             :  *
     240             :  * @param conf holds the backend/plugin configuration
     241             :  * @param errorKey holds the error description in case of failure
     242             :  * @param msgKey holds the master password to be encrypted
     243             :  *
     244             :  * @retval 1 on success
     245             :  * @retval -1 on failure
     246             :  */
     247           1 : int ELEKTRA_PLUGIN_FUNCTION (gpgEncryptMasterPassword) (KeySet * conf, Key * errorKey, Key * msgKey)
     248             : {
     249             :         // [0]: <path to binary>, [argc-3]: --batch, [argc-2]: -e, [argc-1]: NULL-terminator
     250             :         static const kdb_unsigned_short_t staticArgumentsCount = 4;
     251             :         Key * k;
     252             : 
     253             :         // determine the number of total GPG keys to be used
     254           1 :         kdb_unsigned_short_t recipientCount = 0;
     255           1 :         kdb_unsigned_short_t testMode = 0;
     256           1 :         Key * root = ksLookupByName (conf, ELEKTRA_RECIPIENT_KEY, 0);
     257             : 
     258             :         // check root key crypto/key
     259           1 :         if (root && strlen (keyString (root)) > 0)
     260             :         {
     261           1 :                 recipientCount++;
     262             :         }
     263             : 
     264             :         // check for key beneath crypto/key (like crypto/key/#0 etc)
     265           1 :         ksRewind (conf);
     266           5 :         while ((k = ksNext (conf)) != 0)
     267             :         {
     268           3 :                 if (keyIsBelow (k, root) && strlen (keyString (k)) > 0)
     269             :                 {
     270           0 :                         recipientCount++;
     271             :                 }
     272             :         }
     273             : 
     274           1 :         if (recipientCount == 0)
     275             :         {
     276           0 :                 char * errorDescription = ELEKTRA_PLUGIN_FUNCTION (getMissingGpgKeyErrorText) (conf);
     277           0 :                 ELEKTRA_SET_INSTALLATION_ERROR (errorKey, errorDescription);
     278           0 :                 elektraFree (errorDescription);
     279           0 :                 return -1;
     280             :         }
     281             : 
     282           1 :         if (inTestMode (conf))
     283             :         {
     284             :                 // add two parameters for unit testing
     285           1 :                 testMode = 2;
     286             :         }
     287             : 
     288             :         // initialize argument vector for gpg call
     289           1 :         const kdb_unsigned_short_t argc = (2 * recipientCount) + staticArgumentsCount + testMode;
     290           1 :         kdb_unsigned_short_t i = 1;
     291           1 :         char * argv[argc];
     292             : 
     293             :         // append root (crypto/key) as gpg recipient
     294           1 :         if (root && strlen (keyString (root)) > 0)
     295             :         {
     296           1 :                 argv[i++] = "-r";
     297             :                 // NOTE argv[] values will not be modified, so const can be discarded safely
     298           1 :                 argv[i++] = (char *) keyString (root);
     299             :         }
     300             : 
     301             :         // append keys beneath root (crypto/key/#_) as gpg recipients
     302           1 :         ksRewind (conf);
     303           5 :         while ((k = ksNext (conf)) != 0)
     304             :         {
     305           3 :                 const char * kStringVal = keyString (k);
     306           3 :                 if (keyIsBelow (k, root) && strlen (kStringVal) > 0)
     307             :                 {
     308           0 :                         argv[i++] = "-r";
     309             :                         // NOTE argv[] values will not be modified, so const can be discarded safely
     310           0 :                         argv[i++] = (char *) kStringVal;
     311             :                 }
     312             :         }
     313             : 
     314             :         // append option for unit tests
     315           1 :         if (testMode)
     316             :         {
     317           1 :                 argv[i++] = "--trust-model";
     318           1 :                 argv[i++] = "always";
     319             :         }
     320             : 
     321           1 :         argv[i++] = "--batch";
     322           1 :         argv[i++] = "-e";
     323           1 :         argv[i++] = NULL;
     324             : 
     325           1 :         ELEKTRA_ASSERT (i == argc, "invalid number of arguments generated in method gpgEncryptMasterPassword()");
     326             : 
     327             :         // call gpg
     328           1 :         int gpgResult = ELEKTRA_PLUGIN_FUNCTION (gpgCall) (conf, errorKey, msgKey, argv, argc);
     329           1 :         if (gpgResult != 1) // no success
     330             :         {
     331             :                 return gpgResult; // error set by ELEKTRA_PLUGIN_FUNCTION (gpgCall)
     332             :         }
     333             : 
     334             :         // encode result as Base64 string
     335           1 :         char * base64Encoded = NULL;
     336           1 :         int base64Result = ELEKTRA_PLUGIN_FUNCTION (base64Encode) (errorKey, keyValue (msgKey), keyGetValueSize (msgKey), &base64Encoded);
     337           1 :         if (base64Encoded)
     338             :         {
     339           1 :                 keySetString (msgKey, base64Encoded);
     340           1 :                 elektraFree (base64Encoded);
     341             :         }
     342             : 
     343             :         return base64Result;
     344             : }
     345             : 
     346             : /**
     347             :  * @brief call the gpg binary to decrypt the random master password.
     348             :  *
     349             :  * @param conf holds the backend/plugin configuration
     350             :  * @param errorKey holds the error description in case of failure
     351             :  * @param msgKey holds the master password to be decrypted. Note that the content of this key will be modified.
     352             :  *
     353             :  * @retval 1 on success
     354             :  * @retval -1 on failure
     355             :  */
     356           2 : int ELEKTRA_PLUGIN_FUNCTION (gpgDecryptMasterPassword) (KeySet * conf, Key * errorKey, Key * msgKey)
     357             : {
     358           2 :         kdb_octet_t * binaryData = NULL;
     359             :         size_t binaryDataLength;
     360             : 
     361             :         // decode the master password string
     362           2 :         ELEKTRA_PLUGIN_FUNCTION (base64Decode) (errorKey, keyString (msgKey), &binaryData, &binaryDataLength);
     363           2 :         if (binaryData)
     364             :         {
     365           2 :                 keySetBinary (msgKey, binaryData, binaryDataLength);
     366           2 :                 elektraFree (binaryData);
     367             :         }
     368             : 
     369             :         // password decryption
     370           2 :         if (inTestMode (conf))
     371             :         {
     372           2 :                 char * argv[] = { "", "--batch", "--trust-model", "always", "-d", NULL };
     373           2 :                 return ELEKTRA_PLUGIN_FUNCTION (gpgCall) (conf, errorKey, msgKey, argv, 6);
     374             :         }
     375             :         else
     376             :         {
     377           0 :                 char * argv[] = { "", "--batch", "-d", NULL };
     378           0 :                 return ELEKTRA_PLUGIN_FUNCTION (gpgCall) (conf, errorKey, msgKey, argv, 4);
     379             :         }
     380             : }

Generated by: LCOV version 1.13