diff --git a/meson.build b/meson.build index a9ccbc44f..c0463efbc 100644 --- a/meson.build +++ b/meson.build @@ -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') diff --git a/src/fu-engine.c b/src/fu-engine.c index 80a962006..a8f52cbcc 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -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; diff --git a/src/fu-keyring-gpg.c b/src/fu-keyring-gpg.c index af0bfbe01..f06449cf2 100644 --- a/src/fu-keyring-gpg.c +++ b/src/fu-keyring-gpg.c @@ -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), diff --git a/src/fu-keyring-pkcs7.c b/src/fu-keyring-pkcs7.c index e9be22908..07d5460de 100644 --- a/src/fu-keyring-pkcs7.c +++ b/src/fu-keyring-pkcs7.c @@ -8,8 +8,11 @@ #include "config.h" +#include +#include #include +#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; } diff --git a/src/fu-keyring-utils.c b/src/fu-keyring-utils.c index 7d1677b6d..f14068ba4 100644 --- a/src/fu-keyring-utils.c +++ b/src/fu-keyring-utils.c @@ -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), diff --git a/src/fu-keyring.c b/src/fu-keyring.c index d8a88e8c9..1265e4c76 100644 --- a/src/fu-keyring.c +++ b/src/fu-keyring.c @@ -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 * diff --git a/src/fu-keyring.h b/src/fu-keyring.h index 41fddeb75..5c4e37fb0 100644 --- a/src/fu-keyring.h +++ b/src/fu-keyring.h @@ -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, diff --git a/src/fu-self-test.c b/src/fu-self-test.c index 02480e022..8e1f1c67b 100644 --- a/src/fu-self-test.c +++ b/src/fu-self-test.c @@ -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); diff --git a/src/meson.build b/src/meson.build index 6757b7e70..0a8c444ea 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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')