mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-05 06:51:40 +00:00

Use the LVFS key rather than my own GPG key, and load that in the self tests. Fixes https://github.com/hughsie/fwupd/issues/14
626 lines
15 KiB
C
626 lines
15 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* Licensed under the GNU General Public License Version 2
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupd.h>
|
|
#include <gpgme.h>
|
|
|
|
#include "fu-cleanup.h"
|
|
#include "fu-keyring.h"
|
|
|
|
static void fu_keyring_finalize (GObject *object);
|
|
|
|
#define FU_KEYRING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), FU_TYPE_KEYRING, FuKeyringPrivate))
|
|
|
|
/**
|
|
* FuKeyringPrivate:
|
|
*
|
|
* Private #FuKeyring data
|
|
**/
|
|
struct _FuKeyringPrivate
|
|
{
|
|
gpgme_ctx_t ctx;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuKeyring, fu_keyring, G_TYPE_OBJECT)
|
|
|
|
/**
|
|
* fu_keyring_setup:
|
|
**/
|
|
static gboolean
|
|
fu_keyring_setup (FuKeyring *keyring, GError **error)
|
|
{
|
|
gpgme_error_t rc;
|
|
|
|
g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE);
|
|
|
|
if (keyring->priv->ctx != NULL)
|
|
return TRUE;
|
|
|
|
/* check version */
|
|
gpgme_check_version (NULL);
|
|
|
|
/* startup gpgme */
|
|
rc = gpg_err_init ();
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to startup GPG: %s",
|
|
gpgme_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
|
|
/* create a new GPG context */
|
|
rc = gpgme_new (&keyring->priv->ctx);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to create context: %s",
|
|
gpgme_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
|
|
/* set the protocol */
|
|
rc = gpgme_set_protocol (keyring->priv->ctx, GPGME_PROTOCOL_OpenPGP);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to set protocol: %s",
|
|
gpgme_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
|
|
/* enable armor mode */
|
|
gpgme_set_armor (keyring->priv->ctx, TRUE);
|
|
|
|
/* never interactive */
|
|
gpgme_set_pinentry_mode (keyring->priv->ctx, GPGME_PINENTRY_MODE_ERROR);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_list_private_keys:
|
|
**/
|
|
static void
|
|
fu_keyring_list_private_keys (FuKeyring *keyring)
|
|
{
|
|
gpgme_key_t key;
|
|
gpgme_error_t err;
|
|
|
|
err = gpgme_op_keylist_start (keyring->priv->ctx, NULL, 1);
|
|
while (!err) {
|
|
_cleanup_string_free_ GString *str = NULL;
|
|
err = gpgme_op_keylist_next (keyring->priv->ctx, &key);
|
|
if (err)
|
|
break;
|
|
str = g_string_new (key->subkeys->keyid);
|
|
g_string_append_printf (str, "\t[secret:%i, sign:%i]",
|
|
key->subkeys->secret,
|
|
key->subkeys->can_sign);
|
|
if (key->uids && key->uids->name)
|
|
g_string_append_printf (str, " %s", key->uids->name);
|
|
if (key->uids && key->uids->email)
|
|
g_string_append_printf (str, " <%s>", key->uids->email);
|
|
g_debug ("%s", str->str);
|
|
gpgme_key_release (key);
|
|
}
|
|
|
|
if (gpg_err_code (err) != GPG_ERR_EOF) {
|
|
g_warning ("can not list keys: %s\n", gpgme_strerror (err));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_set_signing_key:
|
|
**/
|
|
gboolean
|
|
fu_keyring_set_signing_key (FuKeyring *keyring, const gchar *key_id, GError **error)
|
|
{
|
|
gint n_signers;
|
|
gpgme_error_t rc;
|
|
gpgme_key_t key;
|
|
|
|
|
|
/* setup context */
|
|
if (!fu_keyring_setup (keyring, error))
|
|
return FALSE;
|
|
|
|
/* list possible keys */
|
|
fu_keyring_list_private_keys (keyring);
|
|
|
|
/* find key */
|
|
rc = gpgme_get_key (keyring->priv->ctx, key_id, &key, 1);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to find key %s: %s",
|
|
key_id, gpgme_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
|
|
/* select it to be used */
|
|
gpgme_signers_clear (keyring->priv->ctx);
|
|
rc = gpgme_signers_add (keyring->priv->ctx, key);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to add signing key %s: %s",
|
|
key_id, gpgme_strerror (rc));
|
|
return FALSE;
|
|
}
|
|
gpgme_key_unref (key);
|
|
|
|
/* check it's selected */
|
|
n_signers = gpgme_signers_count (keyring->priv->ctx);
|
|
if (n_signers != 1) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to check signing key %s", key_id);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_add_public_key:
|
|
**/
|
|
gboolean
|
|
fu_keyring_add_public_key (FuKeyring *keyring, const gchar *filename, GError **error)
|
|
{
|
|
gboolean ret = TRUE;
|
|
gpgme_data_t data = NULL;
|
|
gpgme_error_t rc;
|
|
gpgme_import_result_t result;
|
|
gpgme_import_status_t s;
|
|
|
|
g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE);
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
/* import public key */
|
|
g_debug ("Adding public key %s", filename);
|
|
rc = gpgme_data_new_from_file (&data, filename, 1);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to load %s: %s",
|
|
filename, gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
rc = gpgme_op_import (keyring->priv->ctx, data);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to import %s: %s",
|
|
filename, gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
|
|
/* print what keys were imported */
|
|
result = gpgme_op_import_result (keyring->priv->ctx);
|
|
for (s = result->imports; s != NULL; s = s->next) {
|
|
g_debug ("importing key %s [%i] %s",
|
|
s->fpr, s->status, gpgme_strerror (s->result));
|
|
}
|
|
out:
|
|
gpgme_data_release (data);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_add_public_keys:
|
|
**/
|
|
gboolean
|
|
fu_keyring_add_public_keys (FuKeyring *keyring, const gchar *dirname, GError **error)
|
|
{
|
|
_cleanup_dir_close_ GDir *dir = NULL;
|
|
|
|
g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE);
|
|
g_return_val_if_fail (dirname != NULL, FALSE);
|
|
|
|
/* setup context */
|
|
if (!fu_keyring_setup (keyring, error))
|
|
return FALSE;
|
|
|
|
/* search all the public key files */
|
|
dir = g_dir_open (dirname, 0, error);
|
|
if (dir == NULL)
|
|
return FALSE;
|
|
do {
|
|
const gchar *filename;
|
|
_cleanup_free_ gchar *path_tmp = NULL;
|
|
filename = g_dir_read_name (dir);
|
|
if (filename == NULL)
|
|
break;
|
|
path_tmp = g_build_filename (dirname, filename, NULL);
|
|
if (!fu_keyring_add_public_key (keyring, path_tmp, error))
|
|
return FALSE;
|
|
} while (TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_check_signature:
|
|
**/
|
|
static gboolean
|
|
fu_keyring_check_signature (gpgme_signature_t signature, GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
/* look at the signature status */
|
|
switch (gpgme_err_code (signature->status)) {
|
|
case GPG_ERR_NO_ERROR:
|
|
ret = TRUE;
|
|
break;
|
|
case GPG_ERR_SIG_EXPIRED:
|
|
case GPG_ERR_KEY_EXPIRED:
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"valid signature '%s' has expired",
|
|
signature->fpr);
|
|
break;
|
|
case GPG_ERR_CERT_REVOKED:
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"valid signature '%s' has been revoked",
|
|
signature->fpr);
|
|
break;
|
|
case GPG_ERR_BAD_SIGNATURE:
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"'%s' is not a valid signature",
|
|
signature->fpr);
|
|
break;
|
|
case GPG_ERR_NO_PUBKEY:
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"Could not check signature '%s' as no public key",
|
|
signature->fpr);
|
|
break;
|
|
default:
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_SIGNATURE_INVALID,
|
|
"gpgme failed to verify signature '%s'",
|
|
signature->fpr);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_verify_file:
|
|
**/
|
|
gboolean
|
|
fu_keyring_verify_file (FuKeyring *keyring,
|
|
const gchar *filename,
|
|
const gchar *signature,
|
|
GError **error)
|
|
{
|
|
gboolean has_header;
|
|
gboolean ret = TRUE;
|
|
gpgme_data_t data = NULL;
|
|
gpgme_data_t sig = NULL;
|
|
gpgme_error_t rc;
|
|
gpgme_signature_t s;
|
|
gpgme_verify_result_t result;
|
|
_cleanup_string_free_ GString *sig_v1 = NULL;
|
|
|
|
g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE);
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
g_return_val_if_fail (signature != NULL, FALSE);
|
|
|
|
/* setup context */
|
|
if (!fu_keyring_setup (keyring, error))
|
|
return FALSE;
|
|
|
|
/* has header already */
|
|
has_header = g_strstr_len (signature, -1, "BEGIN PGP SIGNATURE") != NULL;
|
|
|
|
/* load file data */
|
|
rc = gpgme_data_new_from_file (&data, filename, 1);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to load %s: %s",
|
|
filename, gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
|
|
/* load signature */
|
|
sig_v1 = g_string_new ("");
|
|
if (!has_header) {
|
|
g_string_append (sig_v1, "-----BEGIN PGP SIGNATURE-----\n");
|
|
g_string_append (sig_v1, "Version: GnuPG v1\n\n");
|
|
}
|
|
g_string_append_printf (sig_v1, "%s\n", signature);
|
|
if (!has_header)
|
|
g_string_append (sig_v1, "-----END PGP SIGNATURE-----\n");
|
|
rc = gpgme_data_new_from_mem (&sig, sig_v1->str, sig_v1->len, 0);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to load signature %s: %s",
|
|
signature, gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
|
|
/* verify */
|
|
rc = gpgme_op_verify (keyring->priv->ctx, sig, data, NULL);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to verify %s: %s",
|
|
filename, gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
|
|
|
|
/* verify the result */
|
|
result = gpgme_op_verify_result (keyring->priv->ctx);
|
|
if (result == NULL) {
|
|
ret = FALSE;
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"no result record from libgpgme");
|
|
goto out;
|
|
}
|
|
|
|
/* look at each signature */
|
|
for (s = result->signatures; s != NULL ; s = s->next ) {
|
|
ret = fu_keyring_check_signature (s, error);
|
|
if (!ret)
|
|
goto out;
|
|
}
|
|
out:
|
|
if (data != NULL)
|
|
gpgme_data_release (data);
|
|
if (sig != NULL)
|
|
gpgme_data_release (sig);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_sign_data:
|
|
**/
|
|
GBytes *
|
|
fu_keyring_sign_data (FuKeyring *keyring, GBytes *payload, GError **error)
|
|
{
|
|
GBytes *sig_bytes = NULL;
|
|
gchar *sig_data = NULL;
|
|
gpgme_data_t data = NULL;
|
|
gpgme_data_t sig = NULL;
|
|
gpgme_error_t rc;
|
|
gpgme_sign_result_t sign_result;
|
|
gsize sig_len = 0;
|
|
|
|
g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE);
|
|
g_return_val_if_fail (payload != NULL, FALSE);
|
|
|
|
/* setup context */
|
|
if (!fu_keyring_setup (keyring, error))
|
|
return NULL;
|
|
|
|
/* load file data */
|
|
rc = gpgme_data_new_from_mem (&data,
|
|
g_bytes_get_data (payload, NULL),
|
|
g_bytes_get_size (payload), 0);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to load data: %s",
|
|
gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
|
|
/* sign */
|
|
gpgme_data_new (&sig);
|
|
rc = gpgme_op_sign (keyring->priv->ctx, data, sig, GPGME_SIG_MODE_DETACH);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to sign data: %s",
|
|
gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
sign_result = gpgme_op_sign_result (keyring->priv->ctx);
|
|
if (sign_result == NULL ||
|
|
sign_result->signatures == NULL ||
|
|
sign_result->signatures->fpr == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to sign data with any key");
|
|
goto out;
|
|
}
|
|
g_debug ("signed with key %s", sign_result->signatures->fpr);
|
|
|
|
/* steal signature data */
|
|
sig_data = gpgme_data_release_and_get_mem (sig, &sig_len);
|
|
sig_bytes = g_bytes_new_with_free_func (sig_data, sig_len, (GDestroyNotify) gpgme_free, sig_data);
|
|
sig = NULL;
|
|
out:
|
|
if (data != NULL)
|
|
gpgme_data_release (data);
|
|
if (sig != NULL)
|
|
gpgme_data_release (sig);
|
|
return sig_bytes;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_verify_data:
|
|
**/
|
|
gboolean
|
|
fu_keyring_verify_data (FuKeyring *keyring,
|
|
GBytes *payload,
|
|
GBytes *payload_signature,
|
|
GError **error)
|
|
{
|
|
gboolean ret = TRUE;
|
|
gpgme_data_t data = NULL;
|
|
gpgme_data_t sig = NULL;
|
|
gpgme_error_t rc;
|
|
gpgme_signature_t s;
|
|
gpgme_verify_result_t result;
|
|
|
|
g_return_val_if_fail (FU_IS_KEYRING (keyring), FALSE);
|
|
g_return_val_if_fail (payload != NULL, FALSE);
|
|
g_return_val_if_fail (payload_signature != NULL, FALSE);
|
|
|
|
/* setup context */
|
|
if (!fu_keyring_setup (keyring, error))
|
|
return FALSE;
|
|
|
|
/* load file data */
|
|
rc = gpgme_data_new_from_mem (&data,
|
|
g_bytes_get_data (payload, NULL),
|
|
g_bytes_get_size (payload), 0);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to load data: %s",
|
|
gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
rc = gpgme_data_new_from_mem (&sig,
|
|
g_bytes_get_data (payload_signature, NULL),
|
|
g_bytes_get_size (payload_signature), 0);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to load signature: %s",
|
|
gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
|
|
/* verify */
|
|
rc = gpgme_op_verify (keyring->priv->ctx, sig, data, NULL);
|
|
if (rc != GPG_ERR_NO_ERROR) {
|
|
ret = FALSE;
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to verify data: %s",
|
|
gpgme_strerror (rc));
|
|
goto out;
|
|
}
|
|
|
|
|
|
/* verify the result */
|
|
result = gpgme_op_verify_result (keyring->priv->ctx);
|
|
if (result == NULL) {
|
|
ret = FALSE;
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"no result record from libgpgme");
|
|
goto out;
|
|
}
|
|
|
|
/* look at each signature */
|
|
for (s = result->signatures; s != NULL ; s = s->next ) {
|
|
ret = fu_keyring_check_signature (s, error);
|
|
if (!ret)
|
|
goto out;
|
|
}
|
|
out:
|
|
if (data != NULL)
|
|
gpgme_data_release (data);
|
|
if (sig != NULL)
|
|
gpgme_data_release (sig);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_class_init:
|
|
**/
|
|
static void
|
|
fu_keyring_class_init (FuKeyringClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = fu_keyring_finalize;
|
|
g_type_class_add_private (klass, sizeof (FuKeyringPrivate));
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_init:
|
|
**/
|
|
static void
|
|
fu_keyring_init (FuKeyring *keyring)
|
|
{
|
|
keyring->priv = FU_KEYRING_GET_PRIVATE (keyring);
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_finalize:
|
|
**/
|
|
static void
|
|
fu_keyring_finalize (GObject *object)
|
|
{
|
|
FuKeyring *keyring = FU_KEYRING (object);
|
|
FuKeyringPrivate *priv = keyring->priv;
|
|
|
|
if (priv->ctx != NULL)
|
|
gpgme_release (priv->ctx);
|
|
|
|
G_OBJECT_CLASS (fu_keyring_parent_class)->finalize (object);
|
|
}
|
|
|
|
/**
|
|
* fu_keyring_new:
|
|
**/
|
|
FuKeyring *
|
|
fu_keyring_new (void)
|
|
{
|
|
FuKeyring *keyring;
|
|
keyring = g_object_new (FU_TYPE_KEYRING, NULL);
|
|
return FU_KEYRING (keyring);
|
|
}
|