]> git.ipfire.org Git - pakfire.git/blob - src/libpakfire/key.c
key: Set path correctly for each GPG context
[pakfire.git] / src / libpakfire / key.c
1 /*#############################################################################
2 # #
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2017 Pakfire development team #
5 # #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 #############################################################################*/
20
21 #include <assert.h>
22 #include <gpgme.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26
27 #include <pakfire/constants.h>
28 #include <pakfire/errno.h>
29 #include <pakfire/i18n.h>
30 #include <pakfire/key.h>
31 #include <pakfire/logging.h>
32 #include <pakfire/pakfire.h>
33 #include <pakfire/util.h>
34
35 #define DEFAULT_KEY_SIZE "rsa4096"
36
37 gpgme_ctx_t pakfire_get_gpgctx(Pakfire pakfire) {
38 static int gpg_initialized = 0;
39 gpgme_error_t error;
40 const char* error_string;
41
42 if (!gpg_initialized) {
43 // Initialise gpgme
44 const char* version = gpgme_check_version(NULL);
45 DEBUG("Loaded gpgme %s\n", version);
46
47 // Check if we support GPG
48 error = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
49 if (gpg_err_code(error) != GPG_ERR_NO_ERROR)
50 goto FAIL;
51
52 // GPG has been initialized
53 gpg_initialized++;
54 }
55
56 // Create a new context
57 gpgme_ctx_t ctx;
58 error = gpgme_new(&ctx);
59 if (gpg_err_code(error) != GPG_ERR_NO_ERROR)
60 goto FAIL;
61
62 // Set output to be ASCII armoured
63 gpgme_set_armor(ctx, 1);
64
65 // Use GPG
66 const char* path = pakfire_get_path(pakfire);
67 char* home = pakfire_path_join(path, "etc/pakfire/gnupg");
68
69 // Check if gpg directories exist
70 if (pakfire_access(home, NULL, R_OK) != 0) {
71 DEBUG("Creating GPG database at %s\n", home);
72
73 int r = pakfire_mkdir(home, S_IRUSR|S_IWUSR|S_IXUSR);
74 if (r) {
75 ERROR("Could not initialize the GPG database at %s\n", home);
76 goto FAIL;
77 }
78 }
79
80 // Setup engine
81 error = gpgme_ctx_set_engine_info(ctx, GPGME_PROTOCOL_OpenPGP, NULL, home);
82 pakfire_free(home);
83 if (gpg_err_code(error) != GPG_ERR_NO_ERROR)
84 goto FAIL;
85
86 gpgme_engine_info_t engine_info = gpgme_ctx_get_engine_info(ctx);
87 DEBUG("GPGME engine info: %s, home = %s\n",
88 engine_info->file_name, engine_info->home_dir);
89
90 return ctx;
91
92 FAIL:
93 gpgme_release(ctx);
94
95 error_string = gpgme_strerror(error);
96 ERROR("%s\n", error_string);
97
98 return NULL;
99 }
100
101 static size_t pakfire_count_keys(Pakfire pakfire) {
102 gpgme_ctx_t gpgctx = pakfire_get_gpgctx(pakfire);
103
104 size_t count = 0;
105
106 gpgme_key_t key = NULL;
107 gpgme_error_t error = gpgme_op_keylist_start(gpgctx, NULL, 0);
108 while (!error) {
109 error = gpgme_op_keylist_next(gpgctx, &key);
110 if (error)
111 break;
112
113 count++;
114
115 gpgme_key_release(key);
116 }
117
118 DEBUG("%zu key(s) in keystore\n", count);
119 gpgme_release(gpgctx);
120
121 return count;
122 }
123
124 PakfireKey* pakfire_key_list(Pakfire pakfire) {
125 size_t count = pakfire_count_keys(pakfire);
126 if (count == 0)
127 return NULL;
128
129 gpgme_ctx_t gpgctx = pakfire_get_gpgctx(pakfire);
130
131 PakfireKey* first = pakfire_calloc(count + 1, sizeof(PakfireKey));
132 PakfireKey* list = first;
133
134 gpgme_key_t gpgkey = NULL;
135 gpgme_error_t error = gpgme_op_keylist_start(gpgctx, NULL, 0);
136 while (!error) {
137 error = gpgme_op_keylist_next(gpgctx, &gpgkey);
138 if (error)
139 break;
140
141 // Add key to the list
142 *list++ = pakfire_key_create(pakfire, gpgkey);
143
144 gpgme_key_release(gpgkey);
145 }
146
147 // Last list item must be NULL
148 *list = NULL;
149
150 gpgme_release(gpgctx);
151
152 return first;
153 }
154
155 PakfireKey pakfire_key_create(Pakfire pakfire, gpgme_key_t gpgkey) {
156 PakfireKey key = pakfire_calloc(1, sizeof(*key));
157
158 if (key) {
159 key->nrefs = 1;
160 key->pakfire = pakfire_ref(pakfire);
161
162 key->gpgkey = gpgkey;
163 gpgme_key_ref(key->gpgkey);
164 }
165
166 return key;
167 }
168
169 static void pakfire_key_free(PakfireKey key) {
170 pakfire_unref(key->pakfire);
171 gpgme_key_unref(key->gpgkey);
172
173 pakfire_free(key);
174 }
175
176 PakfireKey pakfire_key_ref(PakfireKey key) {
177 ++key->nrefs;
178
179 return key;
180 }
181
182 void pakfire_key_unref(PakfireKey key) {
183 if (--key->nrefs > 0)
184 return;
185
186 pakfire_key_free(key);
187 }
188
189 PakfireKey pakfire_key_get(Pakfire pakfire, const char* fingerprint) {
190 gpgme_ctx_t gpgctx = pakfire_get_gpgctx(pakfire);
191
192 gpgme_key_t gpgkey = NULL;
193 gpgme_error_t error = gpgme_get_key(gpgctx, fingerprint, &gpgkey, 1);
194 if (error != GPG_ERR_NO_ERROR)
195 return NULL;
196
197 PakfireKey key = pakfire_key_create(pakfire, gpgkey);
198 gpgme_key_unref(gpgkey);
199 gpgme_release(gpgctx);
200
201 return key;
202 }
203
204 int pakfire_key_delete(PakfireKey key) {
205 gpgme_ctx_t gpgctx = pakfire_get_gpgctx(key->pakfire);
206
207 int r = 0;
208 gpgme_error_t error = gpgme_op_delete(gpgctx, key->gpgkey, 1);
209 if (error != GPG_ERR_NO_ERROR)
210 r = 1;
211
212 gpgme_release(gpgctx);
213
214 return r;
215 }
216
217 const char* pakfire_key_get_fingerprint(PakfireKey key) {
218 return key->gpgkey->fpr;
219 }
220
221 const char* pakfire_key_get_uid(PakfireKey key) {
222 return key->gpgkey->uids->uid;
223 }
224
225 const char* pakfire_key_get_name(PakfireKey key) {
226 return key->gpgkey->uids->name;
227 }
228
229 const char* pakfire_key_get_email(PakfireKey key) {
230 return key->gpgkey->uids->email;
231 }
232
233 const char* pakfire_key_get_pubkey_algo(PakfireKey key) {
234 switch (key->gpgkey->subkeys->pubkey_algo) {
235 case GPGME_PK_RSA:
236 case GPGME_PK_RSA_E:
237 case GPGME_PK_RSA_S:
238 return "RSA";
239
240 case GPGME_PK_DSA:
241 return "DSA";
242
243 case GPGME_PK_ECDSA:
244 return "ECDSA";
245
246 case GPGME_PK_ECDH:
247 return "ECDH";
248
249 case GPGME_PK_ECC:
250 return "ECC";
251
252 case GPGME_PK_EDDSA:
253 return "EDDSA";
254
255 case GPGME_PK_ELG:
256 case GPGME_PK_ELG_E:
257 return "ELG";
258 }
259
260 return NULL;
261 }
262
263 size_t pakfire_key_get_pubkey_length(PakfireKey key) {
264 return key->gpgkey->subkeys->length;
265 }
266
267 time_t pakfire_key_get_created(PakfireKey key) {
268 return key->gpgkey->subkeys->timestamp;
269 }
270
271 time_t pakfire_key_get_expires(PakfireKey key) {
272 return key->gpgkey->subkeys->expires;
273 }
274
275 int pakfire_key_is_revoked(PakfireKey key) {
276 return key->gpgkey->subkeys->revoked;
277 }
278
279 PakfireKey pakfire_key_generate(Pakfire pakfire, const char* userid) {
280 gpgme_ctx_t gpgctx = pakfire_get_gpgctx(pakfire);
281
282 unsigned int flags = 0;
283
284 // Key should be able to be used to sign
285 flags |= GPGME_CREATE_SIGN;
286
287 // Don't set a password
288 flags |= GPGME_CREATE_NOPASSWD;
289
290 // The key should never expire
291 flags |= GPGME_CREATE_NOEXPIRE;
292
293 // Generate the key
294 gpgme_error_t error = gpgme_op_createkey(gpgctx, userid,
295 DEFAULT_KEY_SIZE, 0, 0, NULL, flags);
296
297 if (error != GPG_ERR_NO_ERROR) {
298 printf("ERROR: %s\n", gpgme_strerror(error));
299 return NULL;
300 }
301
302 // Retrieve the result
303 gpgme_genkey_result_t result = gpgme_op_genkey_result(gpgctx);
304 gpgme_release(gpgctx);
305
306 // Retrieve the key by its fingerprint
307 return pakfire_key_get(pakfire, result->fpr);
308 }
309
310 char* pakfire_key_export(PakfireKey key, pakfire_key_export_mode_t mode) {
311 gpgme_ctx_t gpgctx = pakfire_get_gpgctx(key->pakfire);
312
313 gpgme_export_mode_t gpgmode = 0;
314 switch (mode) {
315 case PAKFIRE_KEY_EXPORT_MODE_SECRET:
316 gpgmode |= GPGME_EXPORT_MODE_SECRET;
317 break;
318
319 default:
320 break;
321 }
322
323 const char* fingerprint = pakfire_key_get_fingerprint(key);
324
325 // Initialize the buffer
326 gpgme_data_t keydata;
327 gpgme_error_t error = gpgme_data_new(&keydata);
328 if (gpg_err_code(error) != GPG_ERR_NO_ERROR)
329 goto FAIL;
330
331 // Encode output as ASCII
332 error = gpgme_data_set_encoding(keydata, GPGME_DATA_ENCODING_ARMOR);
333 if (gpg_err_code(error) != GPG_ERR_NO_ERROR)
334 goto FAIL;
335
336 // Copy the key to the buffer
337 error = gpgme_op_export(gpgctx, fingerprint, gpgmode, keydata);
338 if (gpg_err_code(error) != GPG_ERR_NO_ERROR)
339 goto FAIL;
340
341 // Export key in ASCII format
342 size_t length;
343 char* mem = gpgme_data_release_and_get_mem(keydata, &length);
344 gpgme_release(gpgctx);
345
346 // Copy to our own string buffer
347 char* buffer = pakfire_strdup(mem);
348
349 // Release the exported key
350 gpgme_free(mem);
351
352 return buffer;
353
354 FAIL:
355 gpgme_data_release(keydata);
356 gpgme_release(gpgctx);
357
358 return NULL;
359 }
360
361 PakfireKey* pakfire_key_import(Pakfire pakfire, const char* data) {
362 gpgme_error_t error;
363 gpgme_data_t keydata;
364
365 gpgme_ctx_t gpgctx = pakfire_get_gpgctx(pakfire);
366
367 // Form a data object out of the input without copying data
368 error = gpgme_data_new_from_mem(&keydata, data, strlen(data), 0);
369 if (error != GPG_ERR_NO_ERROR)
370 goto FAIL;
371
372 // Try importing the key(s)
373 error = gpgme_op_import(gpgctx, keydata);
374
375 gpgme_import_result_t result;
376 switch (error) {
377 // Everything went fine
378 case GPG_ERR_NO_ERROR:
379 result = gpgme_op_import_result(gpgctx);
380
381 DEBUG("Keys considered = %d\n", result->considered);
382 DEBUG("Keys imported = %d\n", result->imported);
383 DEBUG("Keys not imported = %d\n", result->not_imported);
384
385 // Did we import any keys?
386 gpgme_import_status_t status = result->imports;
387 if (!status)
388 return NULL;
389
390 PakfireKey* head = pakfire_calloc(result->imported + 1, sizeof(*head));
391 PakfireKey* list = head;
392
393 // Retrieve all imported keys
394 while (status) {
395 PakfireKey key = pakfire_key_get(pakfire, status->fpr);
396 if (key) {
397 const char* fingerprint = pakfire_key_get_fingerprint(key);
398 INFO("Imported key %s\n", fingerprint);
399
400 // Append key to list
401 *list++ = key;
402 }
403
404 status = status->next;
405 }
406
407 // Terminate list
408 *list = NULL;
409
410 gpgme_data_release(keydata);
411 gpgme_release(gpgctx);
412
413 return head;
414
415 // Input was invalid
416 case GPG_ERR_INV_VALUE:
417 pakfire_errno = PAKFIRE_E_INVALID_INPUT;
418 break;
419
420 // Fall through for any other errors
421 default:
422 ERROR("Failed with gpgme error: %s\n", gpgme_strerror(error));
423 break;
424 }
425
426 FAIL:
427 gpgme_data_release(keydata);
428 gpgme_release(gpgctx);
429
430 return NULL;
431 }
432
433 char* pakfire_key_dump(PakfireKey key) {
434 char* s = "";
435
436 time_t created = pakfire_key_get_created(key);
437 char* date_created = pakfire_format_date(created);
438
439 asprintf(&s, "pub %s%zu/%s %s",
440 pakfire_key_get_pubkey_algo(key),
441 pakfire_key_get_pubkey_length(key),
442 pakfire_key_get_fingerprint(key),
443 date_created
444 );
445 pakfire_free(date_created);
446
447 const char* uid = pakfire_key_get_uid(key);
448 if (uid) {
449 asprintf(&s, "%s\n %s", s, uid);
450 }
451
452 time_t expires = pakfire_key_get_expires(key);
453 if (expires) {
454 char* date_expires = pakfire_format_date(expires);
455 asprintf(&s, "%s\n %s: %s", s, _("Expires"), date_expires);
456 pakfire_free(date_expires);
457 }
458
459 return s;
460 }