diff --git a/data/tests/colorhug/meson.build b/data/tests/colorhug/meson.build index ba0ae1f39..472c37aca 100644 --- a/data/tests/colorhug/meson.build +++ b/data/tests/colorhug/meson.build @@ -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@', diff --git a/src/fu-engine.c b/src/fu-engine.c index c9b0a4695..9839052cd 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -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 */ diff --git a/src/fu-keyring-gpg.c b/src/fu-keyring-gpg.c index ac01d9903..a24e26853 100644 --- a/src/fu-keyring-gpg.c +++ b/src/fu-keyring-gpg.c @@ -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 diff --git a/src/fu-keyring-pkcs7.c b/src/fu-keyring-pkcs7.c index d094e74e5..0503c4e37 100644 --- a/src/fu-keyring-pkcs7.c +++ b/src/fu-keyring-pkcs7.c @@ -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 diff --git a/src/fu-keyring-result.c b/src/fu-keyring-result.c new file mode 100644 index 000000000..ee0d8d0f6 --- /dev/null +++ b/src/fu-keyring-result.c @@ -0,0 +1,125 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017 Richard Hughes + * + * 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) +{ +} diff --git a/src/fu-keyring-result.h b/src/fu-keyring-result.h new file mode 100644 index 000000000..3d2e3b37a --- /dev/null +++ b/src/fu-keyring-result.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2017 Richard Hughes + * + * 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 + +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 */ diff --git a/src/fu-keyring.c b/src/fu-keyring.c index d83273db4..430da53df 100644 --- a/src/fu-keyring.c +++ b/src/fu-keyring.c @@ -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, diff --git a/src/fu-keyring.h b/src/fu-keyring.h index 9a5c2c25f..103e6c587 100644 --- a/src/fu-keyring.h +++ b/src/fu-keyring.h @@ -25,6 +25,8 @@ #include #include +#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); diff --git a/src/fu-self-test.c b/src/fu-self-test.c index 6fcbb724e..fb47878c0 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * - * Copyright (C) 2015 Richard Hughes + * Copyright (C) 2015-2017 Richard Hughes * * 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"); diff --git a/src/meson.build b/src/meson.build index bcc854243..0c7001e93 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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', ],