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 : }
|