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