fwupd/plugins/synaptics-rmi/fu-synaptics-rmi-common.c
2021-02-08 09:17:03 +00:00

182 lines
4.9 KiB
C

/*
* Copyright (C) 2012 Andrew Duggan
* Copyright (C) 2012 Synaptics Inc.
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gio/gio.h>
#ifdef HAVE_GNUTLS
#include <gnutls/abstract.h>
#include <gnutls/crypto.h>
#endif
#include "fu-common.h"
#include "fu-io-channel.h"
#include "fu-synaptics-rmi-common.h"
#include "fwupd-error.h"
#define RMI_FUNCTION_QUERY_OFFSET 0
#define RMI_FUNCTION_COMMAND_OFFSET 1
#define RMI_FUNCTION_CONTROL_OFFSET 2
#define RMI_FUNCTION_DATA_OFFSET 3
#define RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET 4
#define RMI_FUNCTION_NUMBER 5
#define RMI_FUNCTION_VERSION_MASK 0x60
#define RMI_FUNCTION_INTERRUPT_SOURCES_MASK 0x7
#ifdef HAVE_GNUTLS
typedef guchar gnutls_data_t;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_data_t, gnutls_free)
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pubkey_t, gnutls_pubkey_deinit, NULL)
#pragma clang diagnostic pop
#endif
guint32
fu_synaptics_rmi_generate_checksum (const guint8 *data, gsize len)
{
guint32 lsw = 0xffff;
guint32 msw = 0xffff;
for (gsize i = 0; i < len / 2; i++) {
lsw += fu_common_read_uint16 (&data[i * 2], G_LITTLE_ENDIAN);
msw += lsw;
lsw = (lsw & 0xffff) + (lsw >> 16);
msw = (msw & 0xffff) + (msw >> 16);
}
return msw << 16 | lsw;
}
FuSynapticsRmiFunction *
fu_synaptics_rmi_function_parse (GByteArray *buf, guint16 page_base, guint interrupt_count, GError **error)
{
FuSynapticsRmiFunction *func;
guint8 interrupt_offset;
const guint8 *data = buf->data;
/* not expected */
if (buf->len != RMI_DEVICE_PDT_ENTRY_SIZE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"PDT entry buffer invalid size %u != %i",
buf->len, RMI_DEVICE_PDT_ENTRY_SIZE);
return NULL;
}
func = g_new0 (FuSynapticsRmiFunction, 1);
func->query_base = data[RMI_FUNCTION_QUERY_OFFSET] + page_base;
func->command_base = data[RMI_FUNCTION_COMMAND_OFFSET] + page_base;
func->control_base = data[RMI_FUNCTION_CONTROL_OFFSET] + page_base;
func->data_base = data[RMI_FUNCTION_DATA_OFFSET] + page_base;
func->interrupt_source_count = data[RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET]
& RMI_FUNCTION_INTERRUPT_SOURCES_MASK;
func->function_number = data[RMI_FUNCTION_NUMBER];
func->function_version = (data[RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET]
& RMI_FUNCTION_VERSION_MASK) >> 5;
if (func->interrupt_source_count > 0) {
func->interrupt_reg_num = (interrupt_count + 8) / 8 - 1;
/* set an enable bit for each data source */
interrupt_offset = interrupt_count % 8;
func->interrupt_mask = 0;
for (guint8 i = interrupt_offset; i < (func->interrupt_source_count + interrupt_offset); i++)
func->interrupt_mask |= 1 << i;
}
return func;
}
gboolean
fu_synaptics_rmi_device_writeln (const gchar *fn, const gchar *buf, GError **error)
{
int fd;
g_autoptr(FuIOChannel) io = NULL;
fd = open (fn, O_WRONLY);
if (fd < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"could not open %s", fn);
return FALSE;
}
io = fu_io_channel_unix_new (fd);
return fu_io_channel_write_raw (io, (const guint8 *) buf, strlen (buf),
1000, FU_IO_CHANNEL_FLAG_NONE, error);
}
gboolean
fu_synaptics_verify_sha256_signature (GBytes *payload,
GBytes *pubkey,
GBytes *signature,
GError **error)
{
#ifdef HAVE_GNUTLS
gnutls_datum_t hash;
gnutls_datum_t m;
gnutls_datum_t e;
gnutls_datum_t sig;
gnutls_hash_hd_t sha2;
g_auto(gnutls_pubkey_t) pub = NULL;
gint ec;
guint8 exponent[] = { 1, 0, 1 };
guint hash_length = gnutls_hash_get_len (GNUTLS_DIG_SHA256);
g_autoptr(gnutls_data_t) hash_data = NULL;
/* hash firmware data */
hash_data = gnutls_malloc (hash_length);
gnutls_hash_init (&sha2, GNUTLS_DIG_SHA256);
gnutls_hash (sha2, g_bytes_get_data (payload, NULL), g_bytes_get_size (payload));
gnutls_hash_deinit (sha2, hash_data);
/* hash */
hash.size = hash_length;
hash.data = hash_data;
/* modulus */
m.size = g_bytes_get_size (pubkey);
m.data = (guint8 *) g_bytes_get_data (pubkey, NULL);
/* exponent */
e.size = sizeof(exponent);
e.data = exponent;
/* signature */
sig.size = g_bytes_get_size (signature);
sig.data = (guint8 *) g_bytes_get_data (signature, NULL);
gnutls_pubkey_init (&pub);
ec = gnutls_pubkey_import_rsa_raw (pub, &m, &e);
if (ec < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to import RSA key: %s",
gnutls_strerror (ec));
return FALSE;
}
ec = gnutls_pubkey_verify_hash2 (pub, GNUTLS_SIGN_RSA_SHA256,
0, &hash, &sig);
if (ec < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to verify firmware: %s",
gnutls_strerror (ec));
return FALSE;
}
#endif
/* success */
return TRUE;
}