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