Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief filter plugin providing cryptographic operations using GPGME
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 "gpgme.h"
14 : #include "keylist.h"
15 : #include <gpgme.h>
16 : #include <kdb.h>
17 : #include <kdberrors.h>
18 : #include <kdbtypes.h>
19 : #include <locale.h>
20 : #include <stdlib.h>
21 : #include <string.h>
22 :
23 : /**
24 : * @brief checks if a Key has been marked for encryption by checking the Key's metadata.
25 : *
26 : * If the metakey ELEKTRA_CRYPTO_META_ENCRYPT has the value "1" it is considered to be true.
27 : * Every other value or the non-existence of the metakey is considered to be false.
28 : *
29 : * @param k the Key to be checked
30 : * @retval 0 if the Key has not been marked for encryption
31 : * @retval 1 if the Key has been marked for encryption
32 : */
33 0 : static int isMarkedForEncryption (const Key * k)
34 : {
35 0 : const Key * metaEncrypt = keyGetMeta (k, ELEKTRA_GPGME_META_ENCRYPT);
36 0 : if (metaEncrypt && strcmp (keyString (metaEncrypt), "1") == 0)
37 : {
38 : return 1;
39 : }
40 : return 0;
41 : }
42 :
43 : /**
44 : * @brief checks if a Key contained a binary value before its value has been encrypted by the gpgme plugin.
45 : *
46 : * If the metakey ELEKTRA_GPGME_META_BINARY has the value "1" it is considered to be true.
47 : * Every other value or the non-existence of the metakey is considered to be false.
48 : *
49 : * @param k the Key to be checked
50 : * @retval 0 if the Key contained a string value before encryption
51 : * @retval 1 if the Key contained a binary value before encryption
52 : */
53 0 : static int isOriginallyBinary (const Key * k)
54 : {
55 0 : const Key * metaEncrypt = keyGetMeta (k, ELEKTRA_GPGME_META_BINARY);
56 0 : if (metaEncrypt && strcmp (keyString (metaEncrypt), "1") == 0)
57 : {
58 : return 1;
59 : }
60 : return 0;
61 : }
62 :
63 : /**
64 : * @brief lookup if the test mode for unit testing is enabled.
65 : * @param conf KeySet holding the plugin configuration.
66 : * @retval 0 test mode is not enabled
67 : * @retval 1 test mode is enabled
68 : */
69 0 : static int inTestMode (KeySet * conf)
70 : {
71 0 : Key * k = ksLookupByName (conf, ELEKTRA_GPGME_UNIT_TEST, 0);
72 0 : if (k && !strcmp (keyString (k), "1"))
73 : {
74 : return 1;
75 : }
76 : return 0;
77 : }
78 :
79 : /**
80 : * @brief checks if a given Key k is in the spec namespace.
81 : * @retval 0 if the Key k is in the spec namespace.
82 : * @retval 1 if the Key k is NOT in the spec namespace.
83 : */
84 : static inline int isSpecNamespace (const Key * k)
85 : {
86 0 : return (keyGetNamespace (k) == KEY_NS_SPEC);
87 : }
88 :
89 : /**
90 : * @brief checks if a given key holds a null value.
91 : * @retval 0 if the Key k does not hold a null value.
92 : * @retval 1 if the Key k holds a null value.
93 : */
94 : static inline int isNullValue (const Key * k)
95 : {
96 0 : return keyGetValueSize (k) == 0;
97 : }
98 :
99 : /**
100 : * @brief lookup if the text mode is disabled in the plugin config.
101 : * Text mode is enabled per default.
102 : * @param conf KeySet holding the plugin configuration.
103 : * @retval 0 text mode is not enabled
104 : * @retval 1 text mode is enabled
105 : */
106 0 : static int isTextMode (KeySet * conf)
107 : {
108 0 : Key * k = ksLookupByName (conf, ELEKTRA_GPGME_CONFIG_TEXTMODE, 0);
109 0 : if (k && !strcmp (keyString (k), "0"))
110 : {
111 : return 0;
112 : }
113 : return 1;
114 : }
115 :
116 : /*
117 : * @brief invoke gpgme_key_unref on all keys and free the array.
118 : * @param recipients the array to be released.
119 : */
120 0 : static void freeRecipientArray (gpgme_key_t * recipients)
121 : {
122 0 : unsigned long index = 0;
123 0 : if (recipients)
124 : {
125 0 : while (recipients[index])
126 : {
127 0 : gpgme_key_unref (recipients[index++]);
128 : }
129 0 : elektraFree (recipients);
130 : }
131 0 : }
132 :
133 : /**
134 : * @brief extract all GPG recipients that shall be used for encryption.
135 : * @param config holds the plugin configuration
136 : * @param ctx holds the gpgme context to be used
137 : * @return the recipients as NULL-terminated array of gpgme_key_t, or just NULL if no recipients is specified in config. Must be freed by
138 : * the caller.
139 : */
140 0 : static gpgme_key_t * extractRecipientFromPluginConfig (KeySet * config, Key * errorKey, gpgme_ctx_t ctx)
141 : {
142 : gpgme_error_t err;
143 : gpgme_key_t key;
144 :
145 : keylist_t list;
146 0 : Key * gpgRecipientRoot = ksLookupByName (config, ELEKTRA_RECIPIENT_KEY, 0);
147 :
148 0 : elektraGpgmeKeylistInit (&list);
149 :
150 : // append root (gpg/key) as recipient
151 0 : if (gpgRecipientRoot && strlen (keyString (gpgRecipientRoot)) > 0)
152 : {
153 0 : err = gpgme_get_key (ctx, keyString (gpgRecipientRoot), &key, 0);
154 0 : if (err)
155 : {
156 : // TODO: Correct??
157 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Failed to receive the GPG key. Reason: %s", gpgme_strerror (err));
158 0 : elektraGpgmeKeylistFree (&list);
159 0 : return NULL;
160 : }
161 :
162 0 : if (key)
163 : {
164 0 : if (!elektraGpgmeKeylistAdd (&list, key))
165 : {
166 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (errorKey, "Memory allocation failed");
167 0 : elektraGpgmeKeylistFree (&list);
168 0 : return NULL;
169 : }
170 : }
171 : }
172 :
173 : // append keys beneath root (crypto/key/#_) as recipients
174 0 : if (gpgRecipientRoot)
175 : {
176 : Key * k;
177 :
178 0 : ksRewind (config);
179 0 : while ((k = ksNext (config)) != 0)
180 : {
181 0 : if (keyIsBelow (k, gpgRecipientRoot))
182 : {
183 0 : err = gpgme_get_key (ctx, keyString (k), &key, 0);
184 0 : if (err)
185 : {
186 : // TODO: Correct??
187 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Failed to receive the GPG key. Reason: %s",
188 : gpgme_strerror (err));
189 0 : elektraGpgmeKeylistFree (&list);
190 0 : return NULL;
191 : }
192 :
193 0 : if (key)
194 : {
195 0 : if (!elektraGpgmeKeylistAdd (&list, key))
196 : {
197 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (errorKey, "Memory allocation failed");
198 0 : elektraGpgmeKeylistFree (&list);
199 0 : return NULL;
200 : }
201 : }
202 : }
203 : }
204 : }
205 :
206 0 : if (list.size > 0)
207 : {
208 0 : unsigned long index = 0;
209 : gpgme_key_t tempKey;
210 :
211 : // allocate one extra slot for the NULL-terminator
212 0 : gpgme_key_t * keyArray = elektraMalloc ((list.size + 1) * sizeof (gpgme_key_t));
213 0 : if (!keyArray)
214 : {
215 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (errorKey, "Memory allocation failed");
216 0 : elektraGpgmeKeylistFree (&list);
217 0 : return NULL;
218 : }
219 :
220 0 : elektraGpgmeKeylistRewind (&list);
221 0 : while ((tempKey = elektraGpgmeKeylistNext (&list)))
222 : {
223 0 : keyArray[index++] = tempKey;
224 : }
225 0 : keyArray[index] = NULL;
226 :
227 0 : elektraGpgmeKeylistFree (&list);
228 0 : return keyArray;
229 : }
230 0 : elektraGpgmeKeylistFree (&list);
231 0 : return NULL;
232 : }
233 :
234 : /**
235 : * @brief read out the contents of src and place it to dst.
236 : * @param src the source of type gpgme_data_t
237 : * @param dst the Elektra key of type Key
238 : * @param errorKey will hold an error description in case of failure
239 : * @param textMode set to 1 if the text mode is enabled.
240 : * @retval 1 on success
241 : * @retval -1 on failure
242 : */
243 0 : static int transferGpgmeDataToElektraKey (gpgme_data_t src, Key * dst, Key * errorKey, int textMode)
244 : {
245 0 : int returnValue = 1; // success
246 : off_t ciphertextLen;
247 : ssize_t readCount;
248 0 : char * buffer = NULL;
249 :
250 0 : ciphertextLen = gpgme_data_seek (src, 0, SEEK_END);
251 0 : buffer = (char *) elektraMalloc (ciphertextLen);
252 0 : if (!buffer)
253 : {
254 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (errorKey, "Memory allocation failed");
255 0 : returnValue = -1; // failure
256 0 : goto cleanup;
257 : }
258 :
259 0 : gpgme_data_seek (src, 0, SEEK_SET);
260 0 : readCount = gpgme_data_read (src, buffer, ciphertextLen);
261 0 : if (readCount != ciphertextLen)
262 : {
263 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "An error during occurred during the data transfer. Reason: %s", strerror (errno));
264 0 : returnValue = -1; // failure
265 0 : goto cleanup;
266 : }
267 :
268 0 : if (textMode)
269 : {
270 0 : keySetString (dst, buffer);
271 : }
272 : else
273 : {
274 0 : keySetBinary (dst, buffer, ciphertextLen);
275 : }
276 :
277 : cleanup:
278 0 : if (buffer)
279 : {
280 0 : elektraFree (buffer);
281 : }
282 0 : return returnValue;
283 : }
284 :
285 : /**
286 : * @brief Concatenate the key IDs held by the argument invalidKeys.
287 : * @param result will hold a pointer to the allocated list of key IDs. Must be freed by the caller. Will be set to NULL if the memory
288 : * allocation fails.
289 : * @param invalidKeys the list of gpgme_invalid_key_t elements to concatenate.
290 : */
291 0 : static void generateInvalidKeyErrorMsg (char ** result, gpgme_invalid_key_t invalidKeys)
292 : {
293 0 : size_t length = 0;
294 0 : gpgme_invalid_key_t iterator = invalidKeys;
295 :
296 0 : while (iterator)
297 : {
298 : // + 2 characters: coma and blank -> <keyID>, <keyID>, ...
299 0 : length += strlen (iterator->fpr) + 2;
300 0 : iterator = iterator->next;
301 : }
302 :
303 0 : if (length == 0)
304 : {
305 0 : *result = NULL;
306 0 : return;
307 : }
308 :
309 : // +1 for the NULL terminator
310 0 : *result = elektraMalloc (length + 1);
311 0 : if (!result)
312 : {
313 : // memory allocation error must be handled by the caller
314 : return;
315 : }
316 :
317 : iterator = invalidKeys;
318 0 : while (iterator)
319 : {
320 0 : strncat (*result, iterator->fpr, length);
321 0 : length -= strlen (iterator->fpr);
322 0 : if (iterator->next)
323 : {
324 0 : strncat (*result, ", ", length);
325 0 : length -= 2;
326 : }
327 0 : iterator = iterator->next;
328 : }
329 : }
330 :
331 : /**
332 : * @brief Encrypt all Keys in the KeySet "data" that have a meta-key "gpg/encrpyt" set.
333 : * @see isMarkedForEncryption (const Key * k)
334 : * @param handle holds the plugin configuration.
335 : * @param data the KeySet to be encrypted.
336 : * @param errorKey will hold and error description if an operation fails.
337 : * @retval 1 on success.
338 : * @retval -1 on failure. In this case errorKey will provide a description.
339 : */
340 0 : static int gpgEncrypt (Plugin * handle, KeySet * data, Key * errorKey)
341 : {
342 0 : int returnValue = 1; // success per default
343 : int textMode;
344 : Key * k;
345 :
346 : gpgme_key_t * recipients;
347 : gpgme_ctx_t ctx;
348 : gpgme_error_t err;
349 0 : gpgme_encrypt_flags_t encryptFlags = GPGME_ENCRYPT_NO_ENCRYPT_TO;
350 :
351 0 : err = gpgme_new (&ctx);
352 0 : if (err)
353 : {
354 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Failed to create the gpgme context. Reason: %s", gpgme_strerror (err));
355 0 : return -1; // at this point nothing has been initialized
356 : }
357 :
358 0 : KeySet * pluginConfig = elektraPluginGetConfig (handle);
359 :
360 0 : textMode = isTextMode (pluginConfig);
361 0 : if (textMode)
362 : {
363 0 : gpgme_set_armor (ctx, 1);
364 : }
365 :
366 0 : recipients = extractRecipientFromPluginConfig (pluginConfig, errorKey, ctx);
367 0 : if (!recipients)
368 : {
369 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERROR (errorKey, "No valid recipients were specified");
370 0 : returnValue = -1;
371 0 : goto cleanup;
372 : }
373 :
374 0 : if (inTestMode (pluginConfig))
375 : {
376 0 : encryptFlags |= GPGME_ENCRYPT_ALWAYS_TRUST;
377 : }
378 :
379 0 : ksRewind (data);
380 0 : while ((k = ksNext (data)))
381 : {
382 : gpgme_data_t input;
383 : gpgme_data_t ciphertext;
384 : gpgme_encrypt_result_t result;
385 : gpgme_invalid_key_t invalidKey;
386 :
387 0 : if (!isMarkedForEncryption (k) || isSpecNamespace (k) || isNullValue (k))
388 : {
389 0 : continue;
390 : }
391 :
392 : // preserve the data type of k (string, binary)
393 0 : if (!keyIsBinary (k))
394 : {
395 0 : err = gpgme_data_new_from_mem (&input, keyString (k), strlen (keyString (k)) + 1, 0);
396 : }
397 : else
398 : {
399 0 : keySetMeta (k, ELEKTRA_GPGME_META_BINARY, "1");
400 0 : err = gpgme_data_new_from_mem (&input, keyValue (k), keyGetValueSize (k), 0);
401 : }
402 0 : if (err)
403 : {
404 0 : returnValue = -1;
405 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Internal error: %s", gpgme_strerror (err));
406 0 : goto cleanup;
407 : }
408 :
409 0 : err = gpgme_data_new (&ciphertext);
410 0 : if (err)
411 : {
412 0 : returnValue = -1;
413 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Internal error: %s", gpgme_strerror (err));
414 0 : gpgme_data_release (input);
415 0 : goto cleanup;
416 : }
417 :
418 0 : err = gpgme_op_encrypt (ctx, recipients, encryptFlags, input, ciphertext);
419 0 : if (err)
420 : {
421 0 : returnValue = -1;
422 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Internal error: %s", gpgme_strerror (err));
423 0 : gpgme_data_release (ciphertext);
424 0 : gpgme_data_release (input);
425 0 : goto cleanup;
426 : }
427 :
428 0 : result = gpgme_op_encrypt_result (ctx);
429 0 : invalidKey = result->invalid_recipients;
430 0 : if (invalidKey)
431 : {
432 0 : char * errorMsg = NULL;
433 0 : returnValue = -1;
434 0 : generateInvalidKeyErrorMsg (&errorMsg, invalidKey);
435 0 : if (errorMsg)
436 : {
437 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (errorKey, "Invalid key ID(s): %s", errorMsg);
438 0 : elektraFree (errorMsg);
439 : }
440 : else
441 : {
442 0 : ELEKTRA_SET_OUT_OF_MEMORY_ERROR (errorKey, "Memory allocation failed");
443 : }
444 0 : gpgme_data_release (ciphertext);
445 0 : gpgme_data_release (input);
446 : goto cleanup;
447 : }
448 :
449 : // update Elektra key to encrypted value
450 0 : if (transferGpgmeDataToElektraKey (ciphertext, k, errorKey, textMode) != 1)
451 : {
452 : // error description has been set by transferGpgmeDataToElektraKey()
453 0 : returnValue = -1;
454 0 : gpgme_data_release (ciphertext);
455 0 : gpgme_data_release (input);
456 0 : goto cleanup;
457 : }
458 :
459 0 : gpgme_data_release (ciphertext);
460 0 : gpgme_data_release (input);
461 : }
462 :
463 : cleanup:
464 0 : freeRecipientArray (recipients);
465 0 : gpgme_release (ctx);
466 0 : return returnValue;
467 : }
468 :
469 : /**
470 : * @brief Decrypt all Keys in the KeySet "data" that have a meta-key "gpg/encrpyt" set.
471 : * @see isMarkedForEncryption (const Key * k)
472 : * @param handle holds the plugin configuration.
473 : * @param data the KeySet to be decrypted.
474 : * @param errorKey will hold and error description if an operation fails.
475 : * @retval 1 on success.
476 : * @retval -1 on failure. In this case errorKey will provide a description.
477 : */
478 0 : static int gpgDecrypt (ELEKTRA_UNUSED Plugin * handle, KeySet * data, Key * errorKey)
479 : {
480 0 : int returnValue = 1; // success
481 : Key * k;
482 :
483 : gpgme_ctx_t ctx;
484 : gpgme_error_t err;
485 :
486 0 : err = gpgme_new (&ctx);
487 0 : if (err)
488 : {
489 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Failed to the gpgme context. Reason: %s", gpgme_strerror (err));
490 : return -1; // at this point nothing has been initialized
491 : }
492 :
493 0 : ksRewind (data);
494 0 : while ((k = ksNext (data)) != 0)
495 : {
496 0 : if (!isMarkedForEncryption (k) || isSpecNamespace (k) || isNullValue (k))
497 : {
498 0 : continue;
499 : }
500 :
501 : gpgme_data_t ciphertext;
502 : gpgme_data_t plaintext;
503 0 : int originallyBinary = isOriginallyBinary (k);
504 :
505 0 : err = gpgme_data_new_from_mem (&ciphertext, keyValue (k), keyGetValueSize (k), 0);
506 0 : if (err)
507 : {
508 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Internal error: %s", gpgme_strerror (err));
509 0 : returnValue = -1;
510 0 : goto cleanup;
511 : }
512 :
513 0 : err = gpgme_data_new (&plaintext);
514 0 : if (err)
515 : {
516 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Internal error: %s", gpgme_strerror (err));
517 0 : returnValue = -1;
518 0 : gpgme_data_release (ciphertext);
519 : goto cleanup;
520 : }
521 :
522 0 : err = gpgme_op_decrypt (ctx, ciphertext, plaintext);
523 0 : if (err)
524 : {
525 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Internal error: %s", gpgme_strerror (err));
526 0 : returnValue = -1;
527 0 : gpgme_data_release (plaintext);
528 0 : gpgme_data_release (ciphertext);
529 : goto cleanup;
530 : }
531 :
532 0 : if (transferGpgmeDataToElektraKey (plaintext, k, errorKey, !originallyBinary) != 1)
533 : {
534 : // error description has been set by transferGpgmeDataToElektraKey()
535 0 : returnValue = -1;
536 0 : gpgme_data_release (plaintext);
537 0 : gpgme_data_release (ciphertext);
538 : goto cleanup;
539 : }
540 :
541 0 : gpgme_data_release (plaintext);
542 0 : gpgme_data_release (ciphertext);
543 : }
544 :
545 : cleanup:
546 0 : gpgme_release (ctx);
547 : return returnValue;
548 : }
549 :
550 20 : int elektraGpgmeOpen (ELEKTRA_UNUSED Plugin * handle, ELEKTRA_UNUSED Key * errorKey)
551 : {
552 : gpgme_error_t err;
553 :
554 20 : gpgme_check_version (NULL);
555 : // NOTE the code below is recommended by the gpgme manual
556 : // gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
557 : //#ifndef HAVE_W32_SYSTEM
558 : // gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));
559 : //#endif
560 :
561 20 : err = gpgme_engine_check_version (GPGME_PROTOCOL_OpenPGP);
562 20 : if (err)
563 : {
564 0 : ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "Internal error: %s", gpgme_strerror (err));
565 0 : return -1; // failure
566 : }
567 : return 1; // success
568 : }
569 :
570 20 : int elektraGpgmeClose (ELEKTRA_UNUSED Plugin * handle, ELEKTRA_UNUSED Key * errorKey)
571 : {
572 : // at the moment there is nothing to do
573 20 : return 1; // success
574 : }
575 :
576 20 : int elektraGpgmeGet (Plugin * handle, KeySet * ks, Key * parentKey)
577 : {
578 : // publish the module configuration to Elektra (establish the contract)
579 20 : if (!strcmp (keyName (parentKey), "system/elektra/modules/" ELEKTRA_PLUGIN_NAME))
580 : {
581 20 : KeySet * moduleConfig = ksNew (30,
582 : #include "contract.h"
583 : KS_END);
584 20 : ksAppend (ks, moduleConfig);
585 20 : ksDel (moduleConfig);
586 20 : return 1; // success
587 : }
588 :
589 0 : return gpgDecrypt (handle, ks, parentKey);
590 : }
591 :
592 0 : int elektraGpgmeSet (Plugin * handle, KeySet * ks, Key * parentKey)
593 : {
594 0 : return gpgEncrypt (handle, ks, parentKey);
595 : }
596 :
597 0 : int elektraGpgmeCheckconf (Key * errorKey, KeySet * conf)
598 : {
599 : gpgme_ctx_t ctx;
600 : gpgme_error_t err;
601 :
602 0 : err = gpgme_new (&ctx);
603 0 : if (err)
604 : {
605 0 : ELEKTRA_SET_INSTALLATION_ERRORF (errorKey, "Failed to create the gpgme context. Reason: %s", gpgme_strerror (err));
606 0 : return -1; // at this point nothing has been initialized
607 : }
608 :
609 0 : gpgme_key_t * recipients = extractRecipientFromPluginConfig (conf, errorKey, ctx);
610 0 : gpgme_release (ctx);
611 :
612 0 : if (recipients)
613 : {
614 0 : freeRecipientArray (recipients);
615 : }
616 : else
617 : {
618 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERROR (errorKey, "No valid recipients were specified");
619 0 : return -1; // failure
620 : }
621 0 : return 1; // success
622 : }
623 :
624 20 : Plugin * ELEKTRA_PLUGIN_EXPORT
625 : {
626 : // clang-format off
627 20 : return elektraPluginExport(ELEKTRA_PLUGIN_NAME,
628 : ELEKTRA_PLUGIN_OPEN, &elektraGpgmeOpen,
629 : ELEKTRA_PLUGIN_CLOSE, &elektraGpgmeClose,
630 : ELEKTRA_PLUGIN_GET, &elektraGpgmeGet,
631 : ELEKTRA_PLUGIN_SET, &elektraGpgmeSet,
632 : ELEKTRA_PLUGIN_END);
633 : }
|