1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * Copyright (C) 2013-2015 Richard Hughes <richard@hughsie.com>
4  *
5  * Most of this code was taken from Zif, libzif/zif-transaction.c
6  *
7  * Licensed under the GNU Lesser General Public License Version 2.1
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or(at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
22  */
23 /**
24  * SECTION:dnf-keyring
25  * @short_description: Helper methods for dealing with rpm keyrings.
26  * @include: libdnf.h
27  * @stability: Unstable
28  *
29  * These methods make it easier to deal with rpm keyrings.
30  */
31 
32 
33 #include <stdlib.h>
34 #include <glib.h>
35 #include <rpm/rpmlib.h>
36 #include <rpm/rpmts.h>
37 #include <rpm/rpmlog.h>
38 #include <rpm/rpmcli.h>
39 
40 #include "catch-error.hpp"
41 #include "dnf-types.h"
42 #include "dnf-keyring.h"
43 #include "dnf-utils.h"
44 
45 /**
46  * dnf_keyring_add_public_key:
47  * @keyring: a #rpmKeyring instance.
48  * @filename: The public key filename.
49  * @error: a #GError or %NULL.
50  *
51  * Adds a specific public key to the keyring.
52  *
53  * Returns: %TRUE for success, %FALSE otherwise
54  *
55  * Since: 0.1.0
56  **/
57 gboolean
dnf_keyring_add_public_key(rpmKeyring keyring,const gchar * filename,GError ** error)58 dnf_keyring_add_public_key(rpmKeyring keyring,
59                            const gchar *filename,
60                            GError **error) try
61 {
62     gboolean ret = TRUE;
63     int rc;
64     gsize len;
65     pgpArmor armor;
66     pgpDig dig = NULL;
67     rpmPubkey pubkey = NULL;
68     rpmPubkey *subkeys = NULL;
69     int nsubkeys = 0;
70     uint8_t *pkt = NULL;
71     g_autofree gchar *data = NULL;
72 
73     /* ignore symlinks and directories */
74     if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
75         goto out;
76     if (g_file_test(filename, G_FILE_TEST_IS_SYMLINK))
77         goto out;
78 
79     /* get data */
80     ret = g_file_get_contents(filename, &data, &len, error);
81     if (!ret)
82         goto out;
83 
84     /* rip off the ASCII armor and parse it */
85     armor = pgpParsePkts(data, &pkt, &len);
86     if (armor < 0) {
87         ret = FALSE;
88         g_set_error(error,
89                     DNF_ERROR,
90                     DNF_ERROR_GPG_SIGNATURE_INVALID,
91                     "failed to parse PKI file %s",
92                     filename);
93         goto out;
94     }
95 
96     /* make sure it's something we can add to rpm */
97     if (armor != PGPARMOR_PUBKEY) {
98         ret = FALSE;
99         g_set_error(error,
100                     DNF_ERROR,
101                     DNF_ERROR_GPG_SIGNATURE_INVALID,
102                     "PKI file %s is not a public key",
103                     filename);
104         goto out;
105     }
106 
107     /* test each one */
108     pubkey = rpmPubkeyNew(pkt, len);
109     if (pubkey == NULL) {
110         ret = FALSE;
111         g_set_error(error,
112                     DNF_ERROR,
113                     DNF_ERROR_GPG_SIGNATURE_INVALID,
114                     "failed to parse public key for %s",
115                     filename);
116         goto out;
117     }
118 
119     /* does the key exist in the keyring */
120     dig = rpmPubkeyDig(pubkey);
121     rc = rpmKeyringLookup(keyring, dig);
122     if (rc == RPMRC_OK) {
123         ret = TRUE;
124         g_debug("%s is already present", filename);
125         goto out;
126     }
127 
128     /* add to rpmdb automatically, without a prompt */
129     rc = rpmKeyringAddKey(keyring, pubkey);
130     if (rc == 1) {
131         ret = TRUE;
132         g_debug("%s is already added", filename);
133         goto out;
134     } else if (rc < 0) {
135         ret = FALSE;
136         g_set_error(error,
137                     DNF_ERROR,
138                     DNF_ERROR_GPG_SIGNATURE_INVALID,
139                     "failed to add public key %s to rpmdb",
140                     filename);
141         goto out;
142     }
143 
144     subkeys = rpmGetSubkeys(pubkey, &nsubkeys);
145     for (int i = 0; i < nsubkeys; i++) {
146         rpmPubkey subkey = subkeys[i];
147         if (rpmKeyringAddKey(keyring, subkey) < 0) {
148             ret = FALSE;
149             g_set_error(error,
150                         DNF_ERROR,
151                         DNF_ERROR_GPG_SIGNATURE_INVALID,
152                         "failed to add subkeys for %s to rpmdb",
153                         filename);
154             goto out;
155         }
156     }
157 
158     /* success */
159     g_debug("added missing public key %s to rpmdb", filename);
160     ret = TRUE;
161 out:
162     if (pkt != NULL)
163         free(pkt); /* yes, free() */
164     if (pubkey != NULL)
165         rpmPubkeyFree(pubkey);
166     if (subkeys != NULL) {
167         for (int i = 0; i < nsubkeys; i++) {
168           rpmPubkeyFree(subkeys[i]);
169         }
170         free(subkeys);
171     }
172     if (dig != NULL)
173         pgpFreeDig(dig);
174     return ret;
175 } CATCH_TO_GERROR(FALSE)
176 
177 /**
178  * dnf_keyring_add_public_keys:
179  * @keyring: a #rpmKeyring instance.
180  * @error: a #GError or %NULL.
181  *
182  * Adds all installed public keys to the RPM and shared keyring.
183  *
184  * Returns: %TRUE for success, %FALSE otherwise
185  *
186  * Since: 0.1.0
187  **/
188 gboolean
189 dnf_keyring_add_public_keys(rpmKeyring keyring, GError **error) try
190 {
191     const gchar *gpg_dir = "/etc/pki/rpm-gpg";
192     gboolean ret = TRUE;
193     g_autoptr(GDir) dir = NULL;
194     GError *localError = NULL;
195 
196     /* search all the public key files */
197     dir = g_dir_open(gpg_dir, 0, &localError);
198     if (dir == NULL) {
199         if (localError->domain != G_FILE_ERROR || localError->code != G_FILE_ERROR_NOENT) {
200             g_warning("%s", localError->message);
201         }
202         g_error_free(localError);
203         return TRUE;
204     }
205     do {
206         const gchar *filename;
207         g_autofree gchar *path_tmp = NULL;
208         filename = g_dir_read_name(dir);
209         if (filename == NULL)
210             break;
211         path_tmp = g_build_filename(gpg_dir, filename, NULL);
212         ret = dnf_keyring_add_public_key(keyring, path_tmp, &localError);
213         if (!ret) {
214             g_warning("%s", localError->message);
215             g_error_free(localError);
216         }
217     } while (true);
218     return TRUE;
CATCH_TO_GERROR(FALSE)219 } CATCH_TO_GERROR(FALSE)
220 
221 static int
222 rpmcliverifysignatures_log_handler_cb(rpmlogRec rec, rpmlogCallbackData data)
223 {
224     GString **string =(GString **) data;
225 
226     /* create string if required */
227     if (*string == NULL)
228         *string = g_string_new("");
229 
230     /* if text already exists, join them */
231     if ((*string)->len > 0)
232         g_string_append(*string, ": ");
233     g_string_append(*string, rpmlogRecMessage(rec));
234 
235     /* remove the trailing /n which rpm does */
236     if ((*string)->len > 0)
237         g_string_truncate(*string,(*string)->len - 1);
238     return 0;
239 }
240 
241 /**
242  * dnf_keyring_check_untrusted_file:
243  */
244 gboolean
dnf_keyring_check_untrusted_file(rpmKeyring keyring,const gchar * filename,GError ** error)245 dnf_keyring_check_untrusted_file(rpmKeyring keyring,
246                                  const gchar *filename,
247                                  GError **error) try
248 {
249     FD_t fd = NULL;
250     gboolean ret = FALSE;
251     Header hdr = NULL;
252     pgpDig dig = NULL;
253     rpmRC rc;
254     rpmtd td = NULL;
255     rpmts ts = NULL;
256 
257     char *path = g_strdup(filename);
258     char *path_array[2] = {path, NULL};
259     g_autoptr(GString) rpm_error = NULL;
260 
261     /* open the file for reading */
262     fd = Fopen(filename, "r.fdio");
263     if (fd == NULL) {
264         g_set_error(error,
265                     DNF_ERROR,
266                     DNF_ERROR_FILE_INVALID,
267                     "failed to open %s",
268                     filename);
269         goto out;
270     }
271     if (Ferror(fd)) {
272         g_set_error(error,
273                     DNF_ERROR,
274                     DNF_ERROR_FILE_INVALID,
275                     "failed to open %s: %s",
276                     filename,
277                     Fstrerror(fd));
278         goto out;
279     }
280 
281     ts = rpmtsCreate();
282 
283     if (rpmtsSetKeyring(ts, keyring) < 0) {
284         g_set_error_literal(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR, "failed to set keyring");
285         goto out;
286     }
287     rpmtsSetVfyLevel(ts, RPMSIG_SIGNATURE_TYPE);
288     rpmlogSetCallback(rpmcliverifysignatures_log_handler_cb, &rpm_error);
289 
290     // rpm doesn't provide any better API call than rpmcliVerifySignatures (which is for CLI):
291     // - use path_array as input argument
292     // - gather logs via callback because we don't want to print anything if check is successful
293     if (rpmcliVerifySignatures(ts, (char * const*) path_array)) {
294         g_set_error(error,
295                 DNF_ERROR,
296                 DNF_ERROR_GPG_SIGNATURE_INVALID,
297                 "%s could not be verified.\n%s",
298                 filename,
299                 (rpm_error ? rpm_error->str : "UNKNOWN ERROR"));
300         goto out;
301     }
302 
303     /* read in the file */
304     rc = rpmReadPackageFile(ts, fd, filename, &hdr);
305     if (rc != RPMRC_OK) {
306         /* we only return SHA1 and MD5 failures, as we're not
307          * checking signatures at this stage */
308         g_set_error(error,
309                     DNF_ERROR,
310                     DNF_ERROR_FILE_INVALID,
311                     "%s could not be verified",
312                     filename);
313         goto out;
314     }
315 
316     /* convert and upscale */
317     headerConvert(hdr, HEADERCONV_RETROFIT_V3);
318 
319     /* get RSA key */
320     td = rpmtdNew();
321     rc = static_cast<rpmRC>(headerGet(hdr, RPMTAG_RSAHEADER, td, HEADERGET_MINMEM));
322     if (rc != RPMRC_NOTFOUND) {
323         /* try to read DSA key as a fallback */
324         rc = static_cast<rpmRC>(headerGet(hdr, RPMTAG_DSAHEADER, td, HEADERGET_MINMEM));
325     }
326 
327     /* the package has no signing key */
328     if (rc != RPMRC_NOTFOUND) {
329         g_autofree char *package_filename = g_path_get_basename(filename);
330         ret = FALSE;
331         g_set_error(error,
332                     DNF_ERROR,
333                     DNF_ERROR_GPG_SIGNATURE_INVALID,
334                     "package not signed: %s", package_filename);
335         goto out;
336     }
337 
338     /* make it into a digest */
339     dig = pgpNewDig();
340     rc = static_cast<rpmRC>(pgpPrtPkts(static_cast<const uint8_t *>(td->data), td->count, dig, 0));
341     if (rc != RPMRC_OK) {
342         g_set_error(error,
343                     DNF_ERROR,
344                     DNF_ERROR_FILE_INVALID,
345                     "failed to parse digest header for %s",
346                     filename);
347         goto out;
348     }
349 
350     /* does the key exist in the keyring */
351     rc = rpmKeyringLookup(keyring, dig);
352     if (rc != RPMRC_OK) {
353         g_set_error(error,
354                     DNF_ERROR,
355                     DNF_ERROR_GPG_SIGNATURE_INVALID,
356                     "failed to lookup digest in keyring for %s",
357                     filename);
358         goto out;
359     }
360 
361     /* the package is signed by a key we trust */
362     g_debug("%s has been verified as trusted", filename);
363     ret = TRUE;
364 out:
365     rpmlogSetCallback(NULL, NULL);
366 
367     if (path != NULL)
368         g_free(path);
369     if (dig != NULL)
370         pgpFreeDig(dig);
371     if (td != NULL) {
372         rpmtdFreeData(td);
373         rpmtdFree(td);
374     }
375     if (ts != NULL)
376         rpmtsFree(ts);
377     if (hdr != NULL)
378         headerFree(hdr);
379     if (fd != NULL)
380         Fclose(fd);
381     return ret;
382 } CATCH_TO_GERROR(FALSE)
383