mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-28 18:25:54 +00:00
Allow signing and verifying data using a PKCS-7 self-signed client certificate
This is a per-client certificate and private key that can be used to sign metadata sent to remote servers, for instance success/failure reports.
This commit is contained in:
parent
26e1549d16
commit
f28abe7fda
@ -175,6 +175,9 @@ if get_option('daemon')
|
||||
endif
|
||||
if get_option('pkcs7')
|
||||
gnutls = dependency('gnutls', version : '>= 3.4.4.1')
|
||||
if gnutls.version().version_compare('>= 3.6.0')
|
||||
conf.set('HAVE_GNUTLS_3_6_0', '1')
|
||||
endif
|
||||
conf.set('ENABLE_PKCS7', '1')
|
||||
endif
|
||||
if get_option('gpg')
|
||||
|
@ -2171,7 +2171,8 @@ fu_engine_get_existing_keyring_result (FuEngine *self,
|
||||
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);
|
||||
return fu_keyring_verify_data (kr, blob, blob_sig,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE, error);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2254,7 +2255,9 @@ fu_engine_update_metadata (FuEngine *self, const gchar *remote_id,
|
||||
pki_dir = g_build_filename (sysconfdir, "pki", "fwupd-metadata", NULL);
|
||||
if (!fu_keyring_add_public_keys (kr, pki_dir, error))
|
||||
return FALSE;
|
||||
kr_result = fu_keyring_verify_data (kr, bytes_raw, bytes_sig, error);
|
||||
kr_result = fu_keyring_verify_data (kr, bytes_raw, bytes_sig,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE,
|
||||
error);
|
||||
if (kr_result == NULL)
|
||||
return FALSE;
|
||||
|
||||
|
@ -231,6 +231,7 @@ static FuKeyringResult *
|
||||
fu_keyring_gpg_verify_data (FuKeyring *keyring,
|
||||
GBytes *blob,
|
||||
GBytes *blob_signature,
|
||||
FuKeyringVerifyFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuKeyringGpg *self = FU_KEYRING_GPG (keyring);
|
||||
@ -242,6 +243,15 @@ fu_keyring_gpg_verify_data (FuKeyring *keyring,
|
||||
g_auto(gpgme_data_t) sig = NULL;
|
||||
g_autoptr(GString) authority_newest = g_string_new (NULL);
|
||||
|
||||
/* not supported */
|
||||
if (flags & FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"no GPG client certificate support");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* load file data */
|
||||
rc = gpgme_data_new_from_mem (&data,
|
||||
g_bytes_get_data (blob, NULL),
|
||||
|
@ -8,8 +8,11 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gnutls/abstract.h>
|
||||
#include <gnutls/crypto.h>
|
||||
#include <gnutls/pkcs7.h>
|
||||
|
||||
#include "fu-common.h"
|
||||
#include "fu-keyring-pkcs7.h"
|
||||
|
||||
#include "fwupd-error.h"
|
||||
@ -22,11 +25,20 @@ struct _FuKeyringPkcs7
|
||||
|
||||
G_DEFINE_TYPE (FuKeyringPkcs7, fu_keyring_pkcs7, FU_TYPE_KEYRING)
|
||||
|
||||
typedef guchar gnutls_data_t;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pkcs7_t, gnutls_pkcs7_deinit, NULL)
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_privkey_t, gnutls_privkey_deinit, NULL)
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pubkey_t, gnutls_pubkey_deinit, NULL)
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_crt_t, gnutls_x509_crt_deinit, NULL)
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_dn_t, gnutls_x509_dn_deinit, NULL)
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_privkey_t, gnutls_x509_privkey_deinit, NULL)
|
||||
#ifdef HAVE_GNUTLS_3_6_0
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_spki_t, gnutls_x509_spki_deinit, NULL)
|
||||
#endif
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_data_t, gnutls_free)
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
static gnutls_x509_crt_t
|
||||
@ -151,6 +163,362 @@ fu_keyring_pkcs7_add_public_keys (FuKeyring *keyring,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gnutls_privkey_t
|
||||
fu_keyring_pkcs7_load_privkey (FuKeyringPkcs7 *self, GError **error)
|
||||
{
|
||||
int rc;
|
||||
gnutls_datum_t d = { 0 };
|
||||
gsize bufsz = 0;
|
||||
g_autofree gchar *buf = NULL;
|
||||
g_autofree gchar *fn = NULL;
|
||||
g_autofree gchar *localstatedir = NULL;
|
||||
g_auto(gnutls_privkey_t) key = NULL;
|
||||
|
||||
/* load the private key */
|
||||
rc = gnutls_privkey_init (&key);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"privkey_init: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return NULL;
|
||||
}
|
||||
localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
|
||||
fn = g_build_filename (localstatedir, "pki", "secret.key", NULL);
|
||||
if (!g_file_get_contents (fn, &buf, &bufsz, error))
|
||||
return NULL;
|
||||
d.size = bufsz;
|
||||
d.data = (unsigned char *) buf;
|
||||
rc = gnutls_privkey_import_x509_raw (key, &d, GNUTLS_X509_FMT_PEM, NULL, 0);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"privkey_import_x509_raw: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return NULL;
|
||||
}
|
||||
return g_steal_pointer (&key);
|
||||
}
|
||||
|
||||
static gnutls_x509_crt_t
|
||||
fu_keyring_pkcs7_load_client_certificate (FuKeyringPkcs7 *self, GError **error)
|
||||
{
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autofree gchar *localstatedir = NULL;
|
||||
localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
|
||||
filename = g_build_filename (localstatedir, "pki", "client.pem", NULL);
|
||||
return fu_keyring_pkcs7_load_crt_from_filename (filename,
|
||||
GNUTLS_X509_FMT_PEM,
|
||||
error);
|
||||
}
|
||||
|
||||
static gnutls_pubkey_t
|
||||
fu_keyring_pkcs7_load_pubkey_from_privkey (gnutls_privkey_t privkey, GError **error)
|
||||
{
|
||||
g_auto(gnutls_pubkey_t) pubkey = NULL;
|
||||
int rc;
|
||||
|
||||
/* get the public key part of the private key */
|
||||
rc = gnutls_pubkey_init (&pubkey);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"pubkey_init: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return NULL;
|
||||
}
|
||||
rc = gnutls_pubkey_import_privkey (pubkey, privkey, 0, 0);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"pubkey_import_privkey: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return g_steal_pointer (&pubkey);
|
||||
}
|
||||
|
||||
/* generates a private key just like:
|
||||
* `certtool --generate-privkey` */
|
||||
static gboolean
|
||||
fu_keyring_pkcs7_ensure_private_key (FuKeyringPkcs7 *self, GError **error)
|
||||
{
|
||||
#ifdef HAVE_GNUTLS_3_6_0
|
||||
gnutls_datum_t d = { 0 };
|
||||
int bits;
|
||||
int key_type = GNUTLS_PK_RSA;
|
||||
int rc;
|
||||
g_autofree gchar *fn = NULL;
|
||||
g_autofree gchar *localstatedir = NULL;
|
||||
g_auto(gnutls_x509_privkey_t) key = NULL;
|
||||
g_auto(gnutls_x509_spki_t) spki = NULL;
|
||||
g_autoptr(GFile) file = NULL;
|
||||
g_autoptr(gnutls_data_t) d_payload = NULL;
|
||||
|
||||
/* check exists */
|
||||
localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
|
||||
fn = g_build_filename (localstatedir, "pki", "secret.key", NULL);
|
||||
if (g_file_test (fn, G_FILE_TEST_EXISTS))
|
||||
return TRUE;
|
||||
|
||||
/* initialize key and SPKI */
|
||||
rc = gnutls_x509_privkey_init (&key);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"privkey_init: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
rc = gnutls_x509_spki_init (&spki);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"spki_init: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* generate key */
|
||||
bits = gnutls_sec_param_to_pk_bits (key_type, GNUTLS_SEC_PARAM_HIGH);
|
||||
g_debug ("generating a %d bit %s private key...",
|
||||
bits, gnutls_pk_algorithm_get_name (key_type));
|
||||
rc = gnutls_x509_privkey_generate2(key, key_type, bits, 0, NULL, 0);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"privkey_generate2: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
rc = gnutls_x509_privkey_verify_params (key);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"privkey_verify_params: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* create parents if required */
|
||||
if (!fu_common_mkdir_parent (fn, error))
|
||||
return FALSE;
|
||||
|
||||
/* save to file */
|
||||
rc = gnutls_x509_privkey_export2 (key, GNUTLS_X509_FMT_PEM, &d);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"privkey_export2: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
d_payload = d.data;
|
||||
file = g_file_new_for_path (fn);
|
||||
return g_file_replace_contents (file, (const char *) d_payload, d.size,
|
||||
NULL, FALSE, G_FILE_CREATE_PRIVATE, NULL,
|
||||
NULL, error);
|
||||
#else
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"cannot build private key as GnuTLS version is too old");
|
||||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* generates a self signed certificate just like:
|
||||
* `certtool --generate-self-signed --load-privkey priv.pem` */
|
||||
static gboolean
|
||||
fu_keyring_pkcs7_ensure_client_certificate (FuKeyringPkcs7 *self, GError **error)
|
||||
{
|
||||
int rc;
|
||||
gnutls_datum_t d = { 0 };
|
||||
guchar sha1buf[20];
|
||||
gsize sha1bufsz = sizeof(sha1buf);
|
||||
g_autofree gchar *fn = NULL;
|
||||
g_autofree gchar *localstatedir = NULL;
|
||||
g_auto(gnutls_privkey_t) key = NULL;
|
||||
g_auto(gnutls_pubkey_t) pubkey = NULL;
|
||||
g_auto(gnutls_x509_crt_t) crt = NULL;
|
||||
g_autoptr(gnutls_data_t) d_payload = NULL;
|
||||
|
||||
/* check exists */
|
||||
localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
|
||||
fn = g_build_filename (localstatedir, "pki", "client.pem", NULL);
|
||||
if (g_file_test (fn, G_FILE_TEST_EXISTS))
|
||||
return TRUE;
|
||||
|
||||
/* ensure the private key exists */
|
||||
if (!fu_keyring_pkcs7_ensure_private_key (self, error)) {
|
||||
g_prefix_error (error, "failed to generate private key: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* load private key */
|
||||
key = fu_keyring_pkcs7_load_privkey (self, error);
|
||||
if (key == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* load the public key from the private key */
|
||||
pubkey = fu_keyring_pkcs7_load_pubkey_from_privkey (key, error);
|
||||
if (pubkey == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* create certificate */
|
||||
rc = gnutls_x509_crt_init (&crt);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"crt_init: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set public key */
|
||||
rc = gnutls_x509_crt_set_pubkey (crt, pubkey);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"crt_set_pubkey: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set positive random serial number */
|
||||
rc = gnutls_rnd (GNUTLS_RND_NONCE, sha1buf, sizeof(sha1buf));
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"gnutls_rnd: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
sha1buf[0] &= 0x7f;
|
||||
rc = gnutls_x509_crt_set_serial(crt, sha1buf, sizeof(sha1buf));
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"crt_set_serial: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set activation */
|
||||
rc = gnutls_x509_crt_set_activation_time (crt, time (NULL));
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"set_activation_time: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set expiration */
|
||||
rc = gnutls_x509_crt_set_expiration_time (crt, (time_t) -1);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"set_expiration_time: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set basic constraints */
|
||||
rc = gnutls_x509_crt_set_basic_constraints (crt, 0, -1);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"set_basic_constraints: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set usage */
|
||||
rc = gnutls_x509_crt_set_key_usage (crt, GNUTLS_KEY_DIGITAL_SIGNATURE);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"set_key_usage: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set subject key ID */
|
||||
rc = gnutls_x509_crt_get_key_id (crt, GNUTLS_KEYID_USE_SHA1, sha1buf, &sha1bufsz);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"get_key_id: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
rc = gnutls_x509_crt_set_subject_key_id (crt, sha1buf, sha1bufsz);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"set_subject_key_id: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* set version */
|
||||
rc = gnutls_x509_crt_set_version (crt, 3);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"error setting certificate version: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* self-sign certificate */
|
||||
rc = gnutls_x509_crt_privkey_sign (crt, crt, key, GNUTLS_DIG_SHA256, 0);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"crt_privkey_sign: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* export to file */
|
||||
rc = gnutls_x509_crt_export2 (crt, GNUTLS_X509_FMT_PEM, &d);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"crt_export2: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return FALSE;
|
||||
}
|
||||
d_payload = d.data;
|
||||
return g_file_set_contents (fn, (const gchar *) d_payload, d.size, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_keyring_pkcs7_setup (FuKeyring *keyring, GError **error)
|
||||
{
|
||||
@ -205,10 +573,13 @@ fu_keyring_pkcs7_datum_to_dn_str (const gnutls_datum_t *raw)
|
||||
return g_strndup ((const gchar *) str->data, str->size);
|
||||
}
|
||||
|
||||
/* verifies a detached signature just like:
|
||||
* `certtool --p7-verify --load-certificate client.pem --infile=test.p7b` */
|
||||
static FuKeyringResult *
|
||||
fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
|
||||
GBytes *blob,
|
||||
GBytes *blob_signature,
|
||||
FuKeyringVerifyFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (keyring);
|
||||
@ -217,8 +588,15 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
|
||||
int count;
|
||||
int rc;
|
||||
g_auto(gnutls_pkcs7_t) pkcs7 = NULL;
|
||||
g_auto(gnutls_x509_crt_t) crt = NULL;
|
||||
g_autoptr(GString) authority_newest = g_string_new (NULL);
|
||||
|
||||
/* ensure the client certificate exists */
|
||||
if (!fu_keyring_pkcs7_ensure_client_certificate (self, error)) {
|
||||
g_prefix_error (error, "failed to generate client certificate: ");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* startup */
|
||||
rc = gnutls_pkcs7_init (&pkcs7);
|
||||
if (rc != GNUTLS_E_SUCCESS) {
|
||||
@ -255,17 +633,33 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
|
||||
"no PKCS7 signatures found");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* use client certificate */
|
||||
if (flags & FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT) {
|
||||
if (!fu_keyring_pkcs7_ensure_client_certificate (self, error)) {
|
||||
g_prefix_error (error, "failed to generate client certificate: ");
|
||||
return NULL;
|
||||
}
|
||||
crt = fu_keyring_pkcs7_load_client_certificate (self, error);
|
||||
if (crt == NULL)
|
||||
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 */
|
||||
i, /* index */
|
||||
&datum, /* data */
|
||||
0); /* flags */
|
||||
if (flags & FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT) {
|
||||
rc = gnutls_pkcs7_verify_direct (pkcs7, crt, i, &datum, 0);
|
||||
} else {
|
||||
rc = gnutls_pkcs7_verify (pkcs7, self->tl,
|
||||
NULL, /* vdata */
|
||||
0, /* vdata_size */
|
||||
i, /* index */
|
||||
&datum, /* data */
|
||||
0); /* flags */
|
||||
}
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
@ -303,6 +697,107 @@ fu_keyring_pkcs7_verify_data (FuKeyring *keyring,
|
||||
NULL));
|
||||
}
|
||||
|
||||
/* creates a detached signature just like:
|
||||
* `certtool --p7-detached-sign --load-certificate client.pem \
|
||||
* --load-privkey secret.pem --outfile=test.p7b` */
|
||||
static GBytes *
|
||||
fu_keyring_pkcs7_sign_data (FuKeyring *keyring,
|
||||
GBytes *blob,
|
||||
FuKeyringSignFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuKeyringPkcs7 *self = FU_KEYRING_PKCS7 (keyring);
|
||||
gnutls_datum_t d = { 0 };
|
||||
gnutls_digest_algorithm_t dig = GNUTLS_DIG_NULL;
|
||||
guint gnutls_flags = 0;
|
||||
int rc;
|
||||
g_auto(gnutls_pkcs7_t) pkcs7 = NULL;
|
||||
g_auto(gnutls_privkey_t) key = NULL;
|
||||
g_auto(gnutls_pubkey_t) pubkey = NULL;
|
||||
g_auto(gnutls_x509_crt_t) crt = NULL;
|
||||
g_autoptr(gnutls_data_t) d_payload = NULL;
|
||||
|
||||
/* ensure the client certificate exists */
|
||||
if (!fu_keyring_pkcs7_ensure_client_certificate (self, error)) {
|
||||
g_prefix_error (error, "failed to generate client certificate: ");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* import the keys */
|
||||
crt = fu_keyring_pkcs7_load_client_certificate (self, error);
|
||||
if (crt == NULL)
|
||||
return NULL;
|
||||
key = fu_keyring_pkcs7_load_privkey (self, error);
|
||||
if (key == NULL)
|
||||
return NULL;
|
||||
|
||||
/* get the digest algorithm from the publix key */
|
||||
pubkey = fu_keyring_pkcs7_load_pubkey_from_privkey (key, error);
|
||||
if (pubkey == NULL)
|
||||
return NULL;
|
||||
rc = gnutls_pubkey_get_preferred_hash_algorithm (pubkey, &dig, NULL);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"preferred_hash_algorithm: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* create container */
|
||||
rc = gnutls_pkcs7_init (&pkcs7);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"pkcs7_init: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* sign data */
|
||||
d.data = (unsigned char *) g_bytes_get_data (blob, NULL);
|
||||
d.size = g_bytes_get_size (blob);
|
||||
if (flags & FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP)
|
||||
gnutls_flags |= GNUTLS_PKCS7_INCLUDE_TIME;
|
||||
if (flags & FU_KEYRING_SIGN_FLAG_ADD_CERT)
|
||||
gnutls_flags |= GNUTLS_PKCS7_INCLUDE_CERT;
|
||||
rc = gnutls_pkcs7_sign (pkcs7, crt, key, &d, NULL, NULL, dig, gnutls_flags);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"pkcs7_sign: %s [%i]",
|
||||
gnutls_strerror (rc), rc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* set certificate */
|
||||
if (flags & FU_KEYRING_SIGN_FLAG_ADD_CERT) {
|
||||
rc = gnutls_pkcs7_set_crt (pkcs7, crt);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"pkcs7_set_cr: %s", gnutls_strerror (rc));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* export */
|
||||
rc = gnutls_pkcs7_export2 (pkcs7, GNUTLS_X509_FMT_PEM, &d);
|
||||
if (rc < 0) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_SIGNATURE_INVALID,
|
||||
"pkcs7_export: %s", gnutls_strerror (rc));
|
||||
return NULL;
|
||||
}
|
||||
d_payload = d.data;
|
||||
return g_bytes_new (d_payload, d.size);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_keyring_pkcs7_finalize (GObject *object)
|
||||
{
|
||||
@ -318,6 +813,7 @@ fu_keyring_pkcs7_class_init (FuKeyringPkcs7Class *klass)
|
||||
FuKeyringClass *klass_app = FU_KEYRING_CLASS (klass);
|
||||
klass_app->setup = fu_keyring_pkcs7_setup;
|
||||
klass_app->add_public_keys = fu_keyring_pkcs7_add_public_keys;
|
||||
klass_app->sign_data = fu_keyring_pkcs7_sign_data;
|
||||
klass_app->verify_data = fu_keyring_pkcs7_verify_data;
|
||||
object_class->finalize = fu_keyring_pkcs7_finalize;
|
||||
}
|
||||
|
@ -159,7 +159,9 @@ fu_keyring_get_release_flags (XbNode *release,
|
||||
fu_keyring_get_name (kr));
|
||||
return FALSE;
|
||||
}
|
||||
kr_result = fu_keyring_verify_data (kr, blob_payload, blob_signature, &error_local);
|
||||
kr_result = fu_keyring_verify_data (kr, blob_payload, blob_signature,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE,
|
||||
&error_local);
|
||||
if (kr_result == NULL) {
|
||||
g_warning ("untrusted as failed to verify from %s keyring: %s",
|
||||
fu_keyring_get_name (kr),
|
||||
|
@ -40,13 +40,33 @@ FuKeyringResult *
|
||||
fu_keyring_verify_data (FuKeyring *keyring,
|
||||
GBytes *blob,
|
||||
GBytes *blob_signature,
|
||||
FuKeyringVerifyFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuKeyringClass *klass = FU_KEYRING_GET_CLASS (keyring);
|
||||
g_return_val_if_fail (FU_IS_KEYRING (keyring), NULL);
|
||||
g_return_val_if_fail (blob != NULL, NULL);
|
||||
g_return_val_if_fail (blob_signature != NULL, NULL);
|
||||
return klass->verify_data (keyring, blob, blob_signature, error);
|
||||
return klass->verify_data (keyring, blob, blob_signature, flags, error);
|
||||
}
|
||||
|
||||
GBytes *
|
||||
fu_keyring_sign_data (FuKeyring *keyring,
|
||||
GBytes *blob,
|
||||
FuKeyringSignFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuKeyringClass *klass = FU_KEYRING_GET_CLASS (keyring);
|
||||
g_return_val_if_fail (FU_IS_KEYRING (keyring), NULL);
|
||||
g_return_val_if_fail (blob != NULL, NULL);
|
||||
if (klass->sign_data == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"signing data is not supported");
|
||||
return NULL;
|
||||
}
|
||||
return klass->sign_data (keyring, blob, flags, error);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
|
@ -16,6 +16,19 @@ G_BEGIN_DECLS
|
||||
#define FU_TYPE_KEYRING (fu_keyring_get_type ())
|
||||
G_DECLARE_DERIVABLE_TYPE (FuKeyring, fu_keyring, FU, KEYRING, GObject)
|
||||
|
||||
typedef enum {
|
||||
FU_KEYRING_VERIFY_FLAG_NONE = 0,
|
||||
FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT = 1 << 1,
|
||||
FU_KEYRING_VERIFY_FLAG_LAST
|
||||
} FuKeyringVerifyFlags;
|
||||
|
||||
typedef enum {
|
||||
FU_KEYRING_SIGN_FLAG_NONE = 0,
|
||||
FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP = 1 << 0,
|
||||
FU_KEYRING_SIGN_FLAG_ADD_CERT = 1 << 1,
|
||||
FU_KEYRING_SIGN_FLAG_LAST
|
||||
} FuKeyringSignFlags;
|
||||
|
||||
struct _FuKeyringClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
@ -27,6 +40,11 @@ struct _FuKeyringClass
|
||||
FuKeyringResult *(*verify_data) (FuKeyring *keyring,
|
||||
GBytes *payload,
|
||||
GBytes *payload_signature,
|
||||
FuKeyringVerifyFlags flags,
|
||||
GError **error);
|
||||
GBytes *(*sign_data) (FuKeyring *keyring,
|
||||
GBytes *payload,
|
||||
FuKeyringSignFlags flags,
|
||||
GError **error);
|
||||
};
|
||||
|
||||
@ -38,6 +56,11 @@ gboolean fu_keyring_add_public_keys (FuKeyring *keyring,
|
||||
FuKeyringResult *fu_keyring_verify_data (FuKeyring *keyring,
|
||||
GBytes *blob,
|
||||
GBytes *blob_signature,
|
||||
FuKeyringVerifyFlags flags,
|
||||
GError **error);
|
||||
GBytes *fu_keyring_sign_data (FuKeyring *keyring,
|
||||
GBytes *blob,
|
||||
FuKeyringSignFlags flags,
|
||||
GError **error);
|
||||
const gchar *fu_keyring_get_name (FuKeyring *self);
|
||||
void fu_keyring_set_name (FuKeyring *self,
|
||||
|
@ -2400,7 +2400,9 @@ 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));
|
||||
result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig, &error);
|
||||
result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (result_pass);
|
||||
g_assert_cmpint (fu_keyring_result_get_timestamp (result_pass), == , 1438072952);
|
||||
@ -2413,7 +2415,8 @@ 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);
|
||||
result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig, &error);
|
||||
result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE, &error);
|
||||
g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID);
|
||||
g_assert_null (result_fail);
|
||||
g_clear_error (&error);
|
||||
@ -2463,7 +2466,8 @@ 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);
|
||||
result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig, &error);
|
||||
result_pass = fu_keyring_verify_data (keyring, blob_pass, blob_sig,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (result_pass);
|
||||
g_assert_cmpint (fu_keyring_result_get_timestamp (result_pass), >= , 1502871248);
|
||||
@ -2475,7 +2479,8 @@ fu_keyring_pkcs7_func (void)
|
||||
blob_sig2 = fu_common_get_contents_bytes (sig_fn2, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (blob_sig2);
|
||||
result_fail = fu_keyring_verify_data (keyring, blob_pass, blob_sig2, &error);
|
||||
result_fail = fu_keyring_verify_data (keyring, blob_pass, blob_sig2,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE, &error);
|
||||
g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID);
|
||||
g_assert_null (result_fail);
|
||||
g_clear_error (&error);
|
||||
@ -2486,7 +2491,8 @@ 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);
|
||||
result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig, &error);
|
||||
result_fail = fu_keyring_verify_data (keyring, blob_fail, blob_sig,
|
||||
FU_KEYRING_VERIFY_FLAG_NONE, &error);
|
||||
g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID);
|
||||
g_assert_null (result_fail);
|
||||
g_clear_error (&error);
|
||||
@ -2495,6 +2501,45 @@ fu_keyring_pkcs7_func (void)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
fu_keyring_pkcs7_self_signed_func (void)
|
||||
{
|
||||
#ifdef ENABLE_PKCS7
|
||||
gboolean ret;
|
||||
g_autoptr(FuKeyring) kr = NULL;
|
||||
g_autoptr(FuKeyringResult) kr_result = NULL;
|
||||
g_autoptr(GBytes) payload = NULL;
|
||||
g_autoptr(GBytes) signature = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
#ifndef HAVE_GNUTLS_3_6_0
|
||||
/* required to create the private key correctly */
|
||||
g_test_skip ("GnuTLS version too old");
|
||||
#endif
|
||||
|
||||
/* create detached signature and verify */
|
||||
kr = fu_keyring_pkcs7_new ();
|
||||
ret = fu_keyring_setup (kr, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
payload = fu_common_get_contents_bytes ("/etc/machine-id", &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (payload);
|
||||
signature = fu_keyring_sign_data (kr, payload, FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (signature);
|
||||
ret = fu_common_set_contents_bytes ("/tmp/test.p7b", signature, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
kr_result = fu_keyring_verify_data (kr, payload, signature,
|
||||
FU_KEYRING_VERIFY_FLAG_USE_CLIENT_CERT, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (kr_result);
|
||||
#else
|
||||
g_test_skip ("no GnuTLS support enabled");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
fu_common_firmware_builder_func (void)
|
||||
{
|
||||
@ -3523,6 +3568,7 @@ main (int argc, char **argv)
|
||||
g_test_add_func ("/fwupd/plugin{composite}", fu_plugin_composite_func);
|
||||
g_test_add_func ("/fwupd/keyring{gpg}", fu_keyring_gpg_func);
|
||||
g_test_add_func ("/fwupd/keyring{pkcs7}", fu_keyring_pkcs7_func);
|
||||
g_test_add_func ("/fwupd/keyring{pkcs7-self-signed}", fu_keyring_pkcs7_self_signed_func);
|
||||
g_test_add_func ("/fwupd/plugin{build-hash}", fu_plugin_hash_func);
|
||||
g_test_add_func ("/fwupd/chunk", fu_chunk_func);
|
||||
g_test_add_func ("/fwupd/common{version-guess-format}", fu_common_version_guess_format_func);
|
||||
|
@ -398,7 +398,7 @@ if get_option('tests')
|
||||
'-DFU_MUTEX_DEBUG',
|
||||
],
|
||||
)
|
||||
test('fu-self-test', e, is_parallel:false)
|
||||
test('fu-self-test', e, is_parallel:false, timeout:180)
|
||||
endif
|
||||
|
||||
if get_option('introspection')
|
||||
|
Loading…
Reference in New Issue
Block a user