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