Return the authority and timestamp as part of the signing validation

This means we return an error when encountering a rollback attack. This can
currently be performed by providing the old metadata and old signature when
calling into UpdateMetadata.
This commit is contained in:
Richard Hughes 2017-08-16 12:27:51 +01:00
parent 7cc2679f09
commit f69a4810fa
10 changed files with 330 additions and 27 deletions

View File

@ -18,6 +18,7 @@ colorhug_pkcs7_signature = custom_target('firmware.bin.p7b',
input: 'firmware.bin',
output: 'firmware.bin.p7b',
command: [certtool, '--p7-detached-sign',
'--p7-time',
'--load-privkey', pkcs7_privkey,
'--load-certificate', pkcs7_certificate,
'--infile', '@INPUT@',

View File

@ -304,6 +304,7 @@ fu_engine_get_release_trust_flags (AsRelease *release,
g_autofree gchar *pki_dir = NULL;
g_autoptr(GError) error_local = NULL;
g_autoptr(FuKeyring) kr = NULL;
g_autoptr(FuKeyringResult) kr_result = NULL;
struct {
FwupdKeyringKind kind;
const gchar *ext;
@ -365,7 +366,8 @@ fu_engine_get_release_trust_flags (AsRelease *release,
return FALSE;
if (!fu_keyring_setup (kr, pki_dir, error))
return FALSE;
if (!fu_keyring_verify_data (kr, blob_payload, blob_signature, &error_local)) {
kr_result = fu_keyring_verify_data (kr, blob_payload, blob_signature, &error_local);
if (kr_result == NULL) {
g_warning ("untrusted as failed to verify: %s",
error_local->message);
return TRUE;
@ -1432,6 +1434,23 @@ fu_engine_load_metadata_store (FuEngine *self, GError **error)
return TRUE;
}
static FuKeyringResult *
fu_engine_get_existing_keyring_result (FuEngine *self,
FuKeyring *kr,
FwupdRemote *remote,
GError **error)
{
g_autoptr(GBytes) blob = NULL;
g_autoptr(GBytes) blob_sig = NULL;
blob = fu_common_get_contents_bytes (fwupd_remote_get_filename_cache (remote), error);
if (blob == NULL)
return NULL;
blob_sig = fu_common_get_contents_bytes (fwupd_remote_get_filename_cache_sig (remote), error);
if (blob_sig == NULL)
return NULL;
return fu_keyring_verify_data (kr, blob, blob_sig, error);
}
/**
* fu_engine_update_metadata:
* @self: A #FuEngine
@ -1499,13 +1518,52 @@ fu_engine_update_metadata (FuEngine *self, const gchar *remote_id,
keyring_kind = fwupd_remote_get_keyring_kind (remote);
if (keyring_kind != FWUPD_KEYRING_KIND_NONE) {
g_autoptr(FuKeyring) kr = NULL;
g_autoptr(FuKeyringResult) kr_result = NULL;
g_autoptr(FuKeyringResult) kr_result_old = NULL;
g_autoptr(GError) error_local = NULL;
kr = fu_engine_get_keyring_for_kind (keyring_kind, error);
if (kr == NULL)
return FALSE;
if (!fu_keyring_setup (kr, "/etc/pki/fwupd-metadata", error))
return FALSE;
if (!fu_keyring_verify_data (kr, bytes_raw, bytes_sig, error))
kr_result = fu_keyring_verify_data (kr, bytes_raw, bytes_sig, error);
if (kr_result == NULL)
return FALSE;
/* verify the metadata was signed later than the existing
* metadata for this remote to mitigate a rollback attack */
kr_result_old = fu_engine_get_existing_keyring_result (self, kr,
remote,
&error_local);
if (kr_result_old == NULL) {
if (g_error_matches (error_local,
G_FILE_ERROR,
G_FILE_ERROR_NOENT)) {
g_debug ("no existing valid keyrings: %s",
error_local->message);
} else {
g_warning ("could not get existing keyring result: %s",
error_local->message);
}
} else {
gint64 delta = 0;
if (fu_keyring_result_get_timestamp (kr_result) > 0 &&
fu_keyring_result_get_timestamp (kr_result_old) > 0) {
delta = fu_keyring_result_get_timestamp (kr_result) -
fu_keyring_result_get_timestamp (kr_result_old);
}
if (delta < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"new signing timestamp was %"
G_GINT64_FORMAT " seconds older",
-delta);
return FALSE;
} else if (delta > 0) {
g_debug ("timestamp increased, so no rollback");
}
}
}
/* save XML and signature to remotes.d */

View File

@ -233,7 +233,7 @@ fu_keyring_gpg_check_signature (gpgme_signature_t signature, GError **error)
return ret;
}
static gboolean
static FuKeyringResult *
fu_keyring_gpg_verify_data (FuKeyring *keyring,
GBytes *blob,
GBytes *blob_signature,
@ -243,8 +243,10 @@ fu_keyring_gpg_verify_data (FuKeyring *keyring,
gpgme_error_t rc;
gpgme_signature_t s;
gpgme_verify_result_t result;
gint64 timestamp_newest = 0;
g_auto(gpgme_data_t) data = NULL;
g_auto(gpgme_data_t) sig = NULL;
g_autoptr(GString) authority_newest = g_string_new (NULL);
/* load file data */
rc = gpgme_data_new_from_mem (&data,
@ -256,7 +258,7 @@ fu_keyring_gpg_verify_data (FuKeyring *keyring,
FWUPD_ERROR_INTERNAL,
"failed to load data: %s",
gpgme_strerror (rc));
return FALSE;
return NULL;
}
rc = gpgme_data_new_from_mem (&sig,
g_bytes_get_data (blob_signature, NULL),
@ -267,7 +269,7 @@ fu_keyring_gpg_verify_data (FuKeyring *keyring,
FWUPD_ERROR_INTERNAL,
"failed to load signature: %s",
gpgme_strerror (rc));
return FALSE;
return NULL;
}
/* verify */
@ -278,7 +280,7 @@ fu_keyring_gpg_verify_data (FuKeyring *keyring,
FWUPD_ERROR_INTERNAL,
"failed to verify data: %s",
gpgme_strerror (rc));
return FALSE;
return NULL;
}
@ -289,16 +291,25 @@ fu_keyring_gpg_verify_data (FuKeyring *keyring,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"no result record from libgpgme");
return FALSE;
return NULL;
}
/* look at each signature */
for (s = result->signatures; s != NULL ; s = s->next ) {
g_debug ("returned signature fingerprint %s", s->fpr);
if (!fu_keyring_gpg_check_signature (s, error))
return FALSE;
return NULL;
/* save details about the key for the result */
if ((gint64) s->timestamp > timestamp_newest) {
timestamp_newest = (gint64) s->timestamp;
g_string_assign (authority_newest, s->fpr);
}
}
return TRUE;
return FU_KEYRING_RESULT (g_object_new (FU_TYPE_KEYRING_RESULT,
"timestamp", timestamp_newest,
"authority", authority_newest->str,
NULL));
}
static void

View File

@ -36,6 +36,7 @@ struct _FuKeyringPkcs7
G_DEFINE_TYPE (FuKeyringPkcs7, fu_keyring_pkcs7, FU_TYPE_KEYRING)
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pkcs7_t, gnutls_pkcs7_deinit, NULL)
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_dn_t, gnutls_x509_dn_deinit, NULL)
static gboolean
fu_keyring_pkcs7_add_public_key (FuKeyringPkcs7 *self,
@ -120,7 +121,35 @@ fu_keyring_pkcs7_setup (FuKeyring *keyring, const gchar *public_key_dir, GError
return TRUE;
}
static gboolean
static void
_gnutls_datum_deinit (gnutls_datum_t *d)
{
gnutls_free (d->data);
gnutls_free (d);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_datum_t, _gnutls_datum_deinit)
static gchar *
fu_keyring_pkcs7_datum_to_dn_str (const gnutls_datum_t *raw)
{
g_auto(gnutls_x509_dn_t) dn = NULL;
g_autoptr(gnutls_datum_t) str = NULL;
int rc;
rc = gnutls_x509_dn_init (&dn);
if (rc < 0)
return NULL;
rc = gnutls_x509_dn_import (dn, raw);
if (rc < 0)
return NULL;
str = (gnutls_datum_t *) gnutls_malloc (sizeof (gnutls_datum_t));
rc = gnutls_x509_dn_get_str2 (dn, str, 0);
if (rc < 0)
return NULL;
return g_strndup ((const gchar *) str->data, str->size);
}
static FuKeyringResult *
fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
GBytes *blob,
GBytes *blob_signature,
@ -128,9 +157,11 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
{
FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (keyring);
gnutls_datum_t datum;
gint64 timestamp_newest = 0;
int count;
int rc;
g_auto(gnutls_pkcs7_t) pkcs7 = NULL;
g_autoptr(GString) authority_newest = g_string_new (NULL);
/* startup */
rc = gnutls_pkcs7_init (&pkcs7);
@ -140,7 +171,7 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
FWUPD_ERROR_SIGNATURE_INVALID,
"failed to init pkcs7: %s [%i]",
gnutls_strerror (rc), rc);
return FALSE;
return NULL;
}
/* import the signature */
@ -153,7 +184,7 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
FWUPD_ERROR_SIGNATURE_INVALID,
"failed to import the PKCS7 signature: %s [%i]",
gnutls_strerror (rc), rc);
return FALSE;
return NULL;
}
/* verify the blob */
@ -166,9 +197,13 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
FWUPD_ERROR,
FWUPD_ERROR_SIGNATURE_INVALID,
"no PKCS7 signatures found");
return FALSE;
return NULL;
}
for (gint i = 0; i < count; i++) {
gnutls_pkcs7_signature_info_st info;
gint64 signing_time = 0;
/* verify the data against the detached signature */
rc = gnutls_pkcs7_verify (pkcs7, self->tl,
NULL, /* vdata */
0, /* vdata_size */
@ -181,12 +216,34 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
FWUPD_ERROR_SIGNATURE_INVALID,
"failed to verify data: %s [%i]",
gnutls_strerror (rc), rc);
return FALSE;
return NULL;
}
/* save details about the key for the result */
rc = gnutls_pkcs7_get_signature_info (pkcs7, i, &info);
if (rc < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_SIGNATURE_INVALID,
"failed to get signature info: %s [%i]",
gnutls_strerror (rc), rc);
return NULL;
}
signing_time = info.signing_time > 0 ? (gint64) info.signing_time : 1;
if (signing_time > timestamp_newest) {
g_autofree gchar *dn = NULL;
timestamp_newest = signing_time;
dn = fu_keyring_pkcs7_datum_to_dn_str (&info.issuer_dn);
g_string_assign (authority_newest, dn);
}
gnutls_pkcs7_signature_info_deinit (&info);
}
/* success */
return TRUE;
return FU_KEYRING_RESULT (g_object_new (FU_TYPE_KEYRING_RESULT,
"timestamp", timestamp_newest,
"authority", authority_newest->str,
NULL));
}
static void

125
src/fu-keyring-result.c Normal file
View File

@ -0,0 +1,125 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2017 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-error.h"
#include "fu-keyring-result.h"
struct _FuKeyringResult
{
GObject parent_instance;
gint64 timestamp;
gchar *authority;
};
G_DEFINE_TYPE (FuKeyringResult, fu_keyring_result, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_TIMESTAMP,
PROP_AUTHORITY,
PROP_LAST
};
gint64
fu_keyring_result_get_timestamp (FuKeyringResult *self)
{
g_return_val_if_fail (FU_IS_KEYRING_RESULT (self), 0);
return self->timestamp;
}
const gchar *
fu_keyring_result_get_authority (FuKeyringResult *self)
{
g_return_val_if_fail (FU_IS_KEYRING_RESULT (self), NULL);
return self->authority;
}
static void
fu_keyring_result_finalize (GObject *object)
{
FuKeyringResult *self = FU_KEYRING_RESULT (object);
g_free (self->authority);
G_OBJECT_CLASS (fu_keyring_result_parent_class)->finalize (object);
}
static void
fu_keyring_result_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
FuKeyringResult *self = FU_KEYRING_RESULT (object);
switch (prop_id) {
case PROP_TIMESTAMP:
g_value_set_int64 (value, self->timestamp);
break;
case PROP_AUTHORITY:
g_value_set_string (value, self->authority);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
fu_keyring_result_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
FuKeyringResult *self = FU_KEYRING_RESULT (object);
switch (prop_id) {
case PROP_TIMESTAMP:
self->timestamp = g_value_get_int64 (value);
break;
case PROP_AUTHORITY:
self->authority = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
fu_keyring_result_class_init (FuKeyringResultClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
object_class->get_property = fu_keyring_result_get_property;
object_class->set_property = fu_keyring_result_set_property;
object_class->finalize = fu_keyring_result_finalize;
pspec = g_param_spec_int64 ("timestamp", NULL, NULL,
0, G_MAXINT64, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_TIMESTAMP, pspec);
pspec = g_param_spec_string ("authority", NULL, NULL, NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_AUTHORITY, pspec);
}
static void
fu_keyring_result_init (FuKeyringResult *self)
{
}

38
src/fu-keyring-result.h Normal file
View File

@ -0,0 +1,38 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2017 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.
*/
#ifndef __FU_KEYRING_RESULT_H
#define __FU_KEYRING_RESULT_H
#include <glib-object.h>
G_BEGIN_DECLS
#define FU_TYPE_KEYRING_RESULT (fu_keyring_result_get_type ())
G_DECLARE_FINAL_TYPE (FuKeyringResult, fu_keyring_result, FU, KEYRING_RESULT, GObject)
gint64 fu_keyring_result_get_timestamp (FuKeyringResult *self);
const gchar *fu_keyring_result_get_authority (FuKeyringResult *self);
G_END_DECLS
#endif /* __FU_KEYRING_RESULT_H */

View File

@ -41,7 +41,7 @@ fu_keyring_setup (FuKeyring *keyring, const gchar *public_key_dir, GError **erro
return klass->setup (keyring, public_key_dir, error);
}
gboolean
FuKeyringResult *
fu_keyring_verify_data (FuKeyring *keyring,
GBytes *blob,
GBytes *blob_signature,

View File

@ -25,6 +25,8 @@
#include <glib-object.h>
#include <gio/gio.h>
#include "fu-keyring-result.h"
G_BEGIN_DECLS
#define FU_TYPE_KEYRING (fu_keyring_get_type ())
@ -36,7 +38,7 @@ struct _FuKeyringClass
gboolean (*setup) (FuKeyring *keyring,
const gchar *public_key_dir,
GError **error);
gboolean (*verify_data) (FuKeyring *keyring,
FuKeyringResult *(*verify_data) (FuKeyring *keyring,
GBytes *payload,
GBytes *payload_signature,
GError **error);
@ -45,7 +47,7 @@ struct _FuKeyringClass
gboolean fu_keyring_setup (FuKeyring *keyring,
const gchar *public_key_dir,
GError **error);
gboolean fu_keyring_verify_data (FuKeyring *keyring,
FuKeyringResult *fu_keyring_verify_data (FuKeyring *keyring,
GBytes *blob,
GBytes *blob_signature,
GError **error);

View File

@ -1,6 +1,6 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2015-2017 Richard Hughes <richard@hughsie.com>
*
* Licensed under the GNU General Public License Version 2
*
@ -385,6 +385,8 @@ fu_keyring_gpg_func (void)
g_autofree gchar *fw_pass = NULL;
g_autofree gchar *pki_dir = NULL;
g_autoptr(FuKeyring) keyring = NULL;
g_autoptr(FuKeyringResult) result_fail = NULL;
g_autoptr(FuKeyringResult) result_pass = NULL;
g_autoptr(GBytes) blob_fail = NULL;
g_autoptr(GBytes) blob_pass = NULL;
g_autoptr(GBytes) blob_sig = NULL;
@ -415,9 +417,12 @@ fu_keyring_gpg_func (void)
g_assert_no_error (error);
g_assert_nonnull (blob_pass);
blob_sig = g_bytes_new_static (sig_gpgme, strlen (sig_gpgme));
ret = fu_keyring_verify_data (keyring, blob_pass, blob_sig, &error);
result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig, &error);
g_assert_no_error (error);
g_assert_true (ret);
g_assert_nonnull (result_pass);
g_assert_cmpint (fu_keyring_result_get_timestamp (result_pass), == , 1438072952);
g_assert_cmpstr (fu_keyring_result_get_authority (result_pass), == ,
"3FC6B804410ED0840D8F2F9748A6D80E4538BAC2");
/* verify will fail with GnuPG */
fw_fail = fu_test_get_filename (TESTDATADIR, "colorhug/colorhug-als-3.0.2.cab");
@ -425,9 +430,9 @@ fu_keyring_gpg_func (void)
blob_fail = fu_common_get_contents_bytes (fw_fail, &error);
g_assert_no_error (error);
g_assert_nonnull (blob_fail);
ret = fu_keyring_verify_data (keyring, blob_fail, blob_sig, &error);
result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig, &error);
g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID);
g_assert_false (ret);
g_assert_null (result_fail);
g_clear_error (&error);
#else
g_test_skip ("no GnuPG support enabled");
@ -444,6 +449,8 @@ fu_keyring_pkcs7_func (void)
g_autofree gchar *pki_dir = NULL;
g_autofree gchar *sig_fn = NULL;
g_autoptr(FuKeyring) keyring = NULL;
g_autoptr(FuKeyringResult) result_fail = NULL;
g_autoptr(FuKeyringResult) result_pass = NULL;
g_autoptr(GBytes) blob_fail = NULL;
g_autoptr(GBytes) blob_pass = NULL;
g_autoptr(GBytes) blob_sig = NULL;
@ -468,9 +475,11 @@ fu_keyring_pkcs7_func (void)
blob_sig = fu_common_get_contents_bytes (sig_fn, &error);
g_assert_no_error (error);
g_assert_nonnull (blob_sig);
ret = fu_keyring_verify_data (keyring, blob_pass, blob_sig, &error);
result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig, &error);
g_assert_no_error (error);
g_assert_true (ret);
g_assert_nonnull (result_pass);
g_assert_cmpint (fu_keyring_result_get_timestamp (result_pass), >= , 1502871248);
g_assert_cmpstr (fu_keyring_result_get_authority (result_pass), == , "O=Hughski Limited");
/* verify will fail with GnuTLS */
fw_fail = fu_test_get_filename (TESTDATADIR, "colorhug/colorhug-als-3.0.2.cab");
@ -478,9 +487,9 @@ fu_keyring_pkcs7_func (void)
blob_fail = fu_common_get_contents_bytes (fw_fail, &error);
g_assert_no_error (error);
g_assert_nonnull (blob_fail);
ret = fu_keyring_verify_data (keyring, blob_fail, blob_sig, &error);
result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig, &error);
g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID);
g_assert_false (ret);
g_assert_null (result_fail);
g_clear_error (&error);
#else
g_test_skip ("no GnuTLS support enabled");

View File

@ -97,6 +97,7 @@ executable(
'fu-common.c',
'fu-config.c',
'fu-keyring.c',
'fu-keyring-result.c',
'fu-engine.c',
'fu-main.c',
'fu-hwids.c',
@ -151,6 +152,7 @@ if get_option('enable-tests')
'fu-device.c',
'fu-pending.c',
'fu-keyring.c',
'fu-keyring-result.c',
'fu-plugin.c',
'fu-test.c',
],