Add a 'verify' command that verifies the cryptographic hash of device firmware

Goes some way towards fixing https://github.com/hughsie/fwupd/issues/15
This commit is contained in:
Richard Hughes 2015-06-29 08:43:18 +01:00
parent a1e5d86d20
commit a043c2e376
8 changed files with 329 additions and 0 deletions

View File

@ -54,6 +54,7 @@ G_BEGIN_DECLS
#define FU_DEVICE_KEY_PENDING_STATE "PendingState" /* s */
#define FU_DEVICE_KEY_PENDING_ERROR "PendingError" /* s */
#define FU_DEVICE_KEY_TRUSTED "Trusted" /* t */
#define FU_DEVICE_KEY_FIRMWARE_HASH "FirmwareHash" /* s */
typedef struct _FuDevicePrivate FuDevicePrivate;
typedef struct _FuDevice FuDevice;

View File

@ -782,6 +782,36 @@ fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender,
return;
}
/* return 's' */
if (g_strcmp0 (method_name, "Verify") == 0) {
FuDeviceItem *item = NULL;
const gchar *id = NULL;
_cleanup_error_free_ GError *error = NULL;
const gchar *hash = NULL;
/* check the id exists */
g_variant_get (parameters, "(&s)", &id);
g_debug ("Called %s(%s)", method_name, id);
item = fu_main_get_item_by_id (priv, id);
if (item == NULL) {
g_dbus_method_invocation_return_error (invocation,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no such device %s",
id);
return;
}
if (!fu_provider_verify (item->provider, item->device,
FU_PROVIDER_VERIFY_FLAG_NONE, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error);
return;
}
hash = fu_device_get_metadata (item->device, FU_DEVICE_KEY_FIRMWARE_HASH);
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(s)", hash));
return;
}
/* return '' */
if (g_strcmp0 (method_name, "Update") == 0) {
FuDeviceItem *item = NULL;

View File

@ -222,6 +222,77 @@ out:
g_debug ("Failed to close: %s", error->message);
}
/**
* fu_provider_chug_verify:
**/
static gboolean
fu_provider_chug_verify (FuProvider *provider,
FuDevice *device,
FuProviderVerifyFlags flags,
GError **error)
{
FuProviderChug *provider_chug = FU_PROVIDER_CHUG (provider);
FuProviderChugPrivate *priv = provider_chug->priv;
FuProviderChugItem *item;
gsize len;
_cleanup_error_free_ GError *error_local = NULL;
_cleanup_free_ gchar *hash = NULL;
_cleanup_free_ guint8 *data = NULL;
/* find item */
item = g_hash_table_lookup (priv->devices, fu_device_get_id (device));
if (item == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"cannot find: %s",
fu_device_get_id (device));
return FALSE;
}
#if !CD_CHECK_VERSION(1,2,12)
/* recompile colord */
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Cannot read firmware: colord too old");
return FALSE;
#endif
/* open */
if (!fu_provider_chug_open (item, error))
return FALSE;
/* get the firmware from the device */
g_debug ("ColorHug: Verifying firmware");
#if CD_CHECK_VERSION(1,2,12)
ch_device_queue_read_firmware (priv->device_queue, item->usb_device,
&data, &len);
#endif
fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY);
if (!ch_device_queue_process (priv->device_queue,
CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE,
NULL, &error_local)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"failed to dump firmware: %s",
error_local->message);
g_usb_device_close (item->usb_device, NULL);
return FALSE;
}
/* get the SHA1 hash */
hash = g_compute_checksum_for_data (G_CHECKSUM_SHA1, (guchar *) data, len);
fu_device_set_metadata (device, FU_DEVICE_KEY_FIRMWARE_HASH, hash);
/* we're done here */
if (!g_usb_device_close (item->usb_device, &error_local))
g_debug ("Failed to close: %s", error_local->message);
return TRUE;
}
/**
* fu_provider_chug_update:
**/
@ -593,6 +664,7 @@ fu_provider_chug_class_init (FuProviderChugClass *klass)
provider_class->get_name = fu_provider_chug_get_name;
provider_class->coldplug = fu_provider_chug_coldplug;
provider_class->update_online = fu_provider_chug_update;
provider_class->verify = fu_provider_chug_verify;
object_class->finalize = fu_provider_chug_finalize;
g_type_class_add_private (klass, sizeof (FuProviderChugPrivate));

View File

@ -174,6 +174,52 @@ fu_provider_udev_get_rom_version (const gchar *rom_fn, GError **error)
return NULL;
}
/**
* fu_provider_udev_verify:
**/
static gboolean
fu_provider_udev_verify (FuProvider *provider,
FuDevice *device,
FuProviderVerifyFlags flags,
GError **error)
{
const gchar *rom_fn;
guint8 buffer[4096];
_cleanup_checksum_free_ GChecksum *hash = NULL;
_cleanup_object_unref_ GFile *file = NULL;
_cleanup_object_unref_ GFileInputStream *input_stream = NULL;
/* open the file */
rom_fn = fu_device_get_metadata (device, "RomFilename");
if (rom_fn == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Unable to read firmware from device");
return FALSE;
}
file = g_file_new_for_path (rom_fn);
input_stream = g_file_read (file, NULL, error);
if (input_stream == NULL)
return NULL;
hash = g_checksum_new (G_CHECKSUM_SHA1);
fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY);
while (TRUE) {
gssize sz;
sz = g_input_stream_read (G_INPUT_STREAM (input_stream),
buffer,
sizeof (buffer),
NULL, NULL);
if (sz <= 0)
break;
g_checksum_update (hash, buffer, sz);
}
fu_device_set_metadata (device, FU_DEVICE_KEY_FIRMWARE_HASH,
g_checksum_get_string (hash));
return TRUE;
}
/**
* fu_provider_udev_client_add:
**/
@ -268,6 +314,8 @@ fu_provider_udev_client_add (FuProviderUdev *provider_udev, GUdevDevice *device)
if (vendor != NULL)
fu_device_set_metadata (dev, FU_DEVICE_KEY_VENDOR, vendor);
fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION, version);
if (g_file_test (rom_fn, G_FILE_TEST_EXISTS))
fu_device_set_metadata (dev, "RomFilename", rom_fn);
/* insert to hash */
g_hash_table_insert (provider_udev->priv->devices, g_strdup (id), dev);
@ -353,6 +401,7 @@ fu_provider_udev_class_init (FuProviderUdevClass *klass)
provider_class->get_name = fu_provider_udev_get_name;
provider_class->coldplug = fu_provider_udev_coldplug;
provider_class->verify = fu_provider_udev_verify;
object_class->finalize = fu_provider_udev_finalize;
g_type_class_add_private (klass, sizeof (FuProviderUdevPrivate));

View File

@ -184,6 +184,21 @@ fu_provider_schedule_update (FuProvider *provider,
return fu_provider_offline_setup (error);
}
/**
* fu_provider_verify:
**/
gboolean
fu_provider_verify (FuProvider *provider,
FuDevice *device,
FuProviderVerifyFlags flags,
GError **error)
{
FuProviderClass *klass = FU_PROVIDER_GET_CLASS (provider);
if (klass->verify != NULL)
return klass->verify (provider, device, flags, error);
return TRUE;
}
/**
* fu_provider_update:
**/

View File

@ -52,6 +52,11 @@ typedef enum {
FU_PROVIDER_UPDATE_FLAG_LAST
} FuProviderFlags;
typedef enum {
FU_PROVIDER_VERIFY_FLAG_NONE = 0,
FU_PROVIDER_VERIFY_FLAG_LAST
} FuProviderVerifyFlags;
struct _FuProviderClass
{
GObjectClass parent_class;
@ -60,6 +65,10 @@ struct _FuProviderClass
const gchar *(*get_name) (FuProvider *provider);
gboolean (*coldplug) (FuProvider *provider,
GError **error);
gboolean (*verify) (FuProvider *provider,
FuDevice *device,
FuProviderVerifyFlags flags,
GError **error);
gboolean (*update_online) (FuProvider *provider,
FuDevice *device,
gint fd,
@ -104,6 +113,10 @@ gboolean fu_provider_update (FuProvider *provider,
gint fd_fw,
FuProviderFlags flags,
GError **error);
gboolean fu_provider_verify (FuProvider *provider,
FuDevice *device,
FuProviderVerifyFlags flags,
GError **error);
gboolean fu_provider_clear_results (FuProvider *provider,
FuDevice *device,
GError **error);

View File

@ -319,6 +319,7 @@ fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error)
FU_DEVICE_KEY_FLAGS,
FU_DEVICE_KEY_TRUSTED,
FU_DEVICE_KEY_SIZE,
FU_DEVICE_KEY_FIRMWARE_HASH,
NULL };
const gchar *flags_str[] = {
"Internal",
@ -887,6 +888,119 @@ fu_util_get_results (FuUtilPrivate *priv, gchar **values, GError **error)
return TRUE;
}
/**
* fu_util_verify_internal:
**/
static gboolean
fu_util_verify_internal (FuUtilPrivate *priv, const gchar *id, GError **error)
{
_cleanup_variant_unref_ GVariant *val = NULL;
g_dbus_proxy_call (priv->proxy,
"Verify",
g_variant_new ("(s)", id),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
fu_util_get_devices_cb, priv);
g_main_loop_run (priv->loop);
if (priv->val == NULL) {
g_dbus_error_strip_remote_error (priv->error);
g_propagate_error (error, priv->error);
priv->error = NULL;
return FALSE;
}
return TRUE;
}
/**
* fu_util_verify_all:
**/
static gboolean
fu_util_verify_all (FuUtilPrivate *priv, GError **error)
{
AsApp *app;
FuDevice *dev;
const gchar *tmp;
guint i;
_cleanup_object_unref_ AsStore *store = NULL;
_cleanup_ptrarray_unref_ GPtrArray *devices = NULL;
/* get devices from daemon */
devices = fu_util_get_devices_internal (priv, error);
if (devices == NULL)
return FALSE;
/* get results */
for (i = 0; i < devices->len; i++) {
_cleanup_error_free_ GError *error_local = NULL;
dev = g_ptr_array_index (devices, i);
if (!fu_util_verify_internal (priv, fu_device_get_id (dev), &error_local)) {
g_print ("Failed to verify %s: %s\n",
fu_device_get_id (dev),
error_local->message);
}
}
/* only load firmware from the system */
store = as_store_new ();
as_store_add_filter (store, AS_ID_KIND_FIRMWARE);
if (!as_store_load (store, AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM, NULL, error))
return FALSE;
/* print */
for (i = 0; i < devices->len; i++) {
const gchar *hash = NULL;
const gchar *ver = NULL;
_cleanup_free_ gchar *status = NULL;
dev = g_ptr_array_index (devices, i);
hash = fu_device_get_metadata (dev, FU_DEVICE_KEY_FIRMWARE_HASH);
if (hash == NULL)
continue;
app = as_store_get_app_by_id (store, fu_device_get_guid (dev));
if (app == NULL) {
status = g_strdup ("No metadata");
} else {
AsRelease *rel;
ver = fu_device_get_metadata (dev, FU_DEVICE_KEY_VERSION);
rel = as_app_get_release (app, ver);
if (rel == NULL) {
status = g_strdup_printf ("No version %s", ver);
} else {
tmp = as_release_get_checksum (rel, G_CHECKSUM_SHA1);
if (g_strcmp0 (tmp, hash) != 0) {
status = g_strdup_printf ("Failed: for v%s expected %s", ver, tmp);
} else {
status = g_strdup ("OK");
}
}
}
g_print ("%s\t%s\t%s\n", fu_device_get_guid (dev), hash, status);
}
return TRUE;
}
/**
* fu_util_verify:
**/
static gboolean
fu_util_verify (FuUtilPrivate *priv, gchar **values, GError **error)
{
if (g_strv_length (values) == 0)
return fu_util_verify_all (priv, error);
if (g_strv_length (values) != 1) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Invalid arguments: expected 'id'");
return FALSE;
}
return fu_util_verify_internal (priv, values[0], error);
}
/**
* fu_util_print_data:
**/
@ -1127,6 +1241,12 @@ main (int argc, char *argv[])
/* TRANSLATORS: command description */
_("Gets the list of updates for connected hardware"),
fu_util_get_updates);
fu_util_add (priv->cmd_array,
"verify",
NULL,
/* TRANSLATORS: command description */
_("Gets the cryptographic hash of the dumped firmware"),
fu_util_verify);
fu_util_add (priv->cmd_array,
"clear-results",
NULL,

View File

@ -122,6 +122,35 @@
</arg>
</method>
<!--***********************************************************-->
<method name='Verify'>
<doc:doc>
<doc:description>
<doc:para>
Verifies firmware on a device by reading it back and performing
a cryptographic hash, typically SHA1.
</doc:para>
</doc:description>
</doc:doc>
<arg type='s' name='id' direction='in'>
<doc:doc>
<doc:summary>
<doc:para>
An ID, typically a GUID of the hardware.</doc:para>
</doc:summary>
</doc:doc>
</arg>
<arg type='s' name='hash' direction='out'>
<doc:doc>
<doc:summary>
<doc:para>
The cryptographic hash of the firmware.
</doc:para>
</doc:summary>
</doc:doc>
</arg>
</method>
<!--***********************************************************-->
<method name='GetResults'>
<doc:doc>