/* * Copyright (C) 2015 Red Hat, Inc. * * SPDX-License-Identifier: LGPL-2.0+ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "config.h" #include #include "libglnx.h" #include "ostree-gpg-verify-result-private.h" /** * SECTION: ostree-gpg-verify-result * @title: GPG signature verification results * @short_description: Inspect detached GPG signatures * * #OstreeGpgVerifyResult contains verification details for GPG signatures * read from a detached #OstreeRepo metadata object. * * Use ostree_gpg_verify_result_count_all() and * ostree_gpg_verify_result_count_valid() to quickly check overall signature * validity. * * Use ostree_gpg_verify_result_lookup() to find a signature by the key ID * or fingerprint of the signing key. * * For more in-depth inspection, such as presenting signature details to the * user, pass an array of attribute values to ostree_gpg_verify_result_get() * or get all signature details with ostree_gpg_verify_result_get_all(). */ typedef struct { GObjectClass parent_class; } OstreeGpgVerifyResultClass; /* This must stay synchronized with the enum declaration. */ static OstreeGpgSignatureAttr all_signature_attrs[] = { OSTREE_GPG_SIGNATURE_ATTR_VALID, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT, OSTREE_GPG_SIGNATURE_ATTR_TIMESTAMP, OSTREE_GPG_SIGNATURE_ATTR_EXP_TIMESTAMP, OSTREE_GPG_SIGNATURE_ATTR_PUBKEY_ALGO_NAME, OSTREE_GPG_SIGNATURE_ATTR_HASH_ALGO_NAME, OSTREE_GPG_SIGNATURE_ATTR_USER_NAME, OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY, }; static void ostree_gpg_verify_result_initable_iface_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE (OstreeGpgVerifyResult, ostree_gpg_verify_result, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, ostree_gpg_verify_result_initable_iface_init)) static gboolean signature_is_valid (gpgme_signature_t signature) { /* Mimic the way librepo tests for a valid signature, checking both * summary and status fields. * * - VALID summary flag means the signature is fully valid. * - GREEN summary flag means the signature is valid with caveats. * - No summary but also no error means the signature is valid but * the signing key is not certified with a trusted signature. */ return (signature->summary & GPGME_SIGSUM_VALID) || (signature->summary & GPGME_SIGSUM_GREEN) || (signature->summary == 0 && signature->status == GPG_ERR_NO_ERROR); } static gboolean signing_key_is_revoked (gpgme_signature_t signature) { /* In my testing, GPGME does not set the GPGME_SIGSUM_KEY_REVOKED summary * bit on a revoked signing key but rather GPGME_SIGSUM_SYS_ERROR and the * status field shows GPG_ERR_CERT_REVOKED. Turns out GPGME is expecting * GPG_ERR_CERT_REVOKED in the validity_reason field which would then set * the summary bit. * * Reported to GPGME: https://bugs.g10code.com/gnupg/issue1929 */ return (signature->summary & GPGME_SIGSUM_KEY_REVOKED) || ((signature->summary & GPGME_SIGSUM_SYS_ERROR) && gpgme_err_code (signature->status) == GPG_ERR_CERT_REVOKED); } static void ostree_gpg_verify_result_finalize (GObject *object) { OstreeGpgVerifyResult *result = OSTREE_GPG_VERIFY_RESULT (object); if (result->context != NULL) gpgme_release (result->context); if (result->details != NULL) gpgme_result_unref (result->details); G_OBJECT_CLASS (ostree_gpg_verify_result_parent_class)->finalize (object); } static gboolean ostree_gpg_verify_result_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { OstreeGpgVerifyResult *result = OSTREE_GPG_VERIFY_RESULT (initable); gpgme_error_t gpg_error; gboolean ret = FALSE; gpg_error = gpgme_new (&result->context); if (gpg_error != GPG_ERR_NO_ERROR) { ot_gpgme_throw (gpg_error, error, "Unable to create context"); goto out; } ret = TRUE; out: return ret; } static void ostree_gpg_verify_result_class_init (OstreeGpgVerifyResultClass *class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (class); object_class->finalize = ostree_gpg_verify_result_finalize; } static void ostree_gpg_verify_result_init (OstreeGpgVerifyResult *result) { } static void ostree_gpg_verify_result_initable_iface_init (GInitableIface *iface) { iface->init = ostree_gpg_verify_result_initable_init; } /** * ostree_gpg_verify_result_count_all: * @result: an #OstreeGpgVerifyResult * * Counts all the signatures in @result. * * Returns: signature count */ guint ostree_gpg_verify_result_count_all (OstreeGpgVerifyResult *result) { gpgme_signature_t signature; guint count = 0; g_return_val_if_fail (OSTREE_IS_GPG_VERIFY_RESULT (result), 0); for (signature = result->details->signatures; signature != NULL; signature = signature->next) { count++; } return count; } /** * ostree_gpg_verify_result_count_valid: * @result: an #OstreeGpgVerifyResult * * Counts only the valid signatures in @result. * * Returns: valid signature count */ guint ostree_gpg_verify_result_count_valid (OstreeGpgVerifyResult *result) { gpgme_signature_t signature; guint count = 0; g_return_val_if_fail (OSTREE_IS_GPG_VERIFY_RESULT (result), 0); for (signature = result->details->signatures; signature != NULL; signature = signature->next) { if (signature_is_valid (signature)) count++; } return count; } /** * ostree_gpg_verify_result_lookup: * @result: an #OstreeGpgVerifyResult * @key_id: a GPG key ID or fingerprint * @out_signature_index: (out): return location for the index of the signature * signed by @key_id, or %NULL * * Searches @result for a signature signed by @key_id. If a match is found, * the function returns %TRUE and sets @out_signature_index so that further * signature details can be obtained through ostree_gpg_verify_result_get(). * If no match is found, the function returns %FALSE and leaves * @out_signature_index unchanged. * * Returns: %TRUE on success, %FALSE on failure **/ gboolean ostree_gpg_verify_result_lookup (OstreeGpgVerifyResult *result, const gchar *key_id, guint *out_signature_index) { g_auto(gpgme_key_t) lookup_key = NULL; gpgme_signature_t signature; guint signature_index; g_return_val_if_fail (OSTREE_IS_GPG_VERIFY_RESULT (result), FALSE); g_return_val_if_fail (key_id != NULL, FALSE); /* fetch requested key_id from keyring to canonicalise ID */ (void) gpgme_get_key (result->context, key_id, &lookup_key, 0); if (lookup_key == NULL) { g_debug ("Could not find key ID %s to lookup signature.", key_id); return FALSE; } for (signature = result->details->signatures, signature_index = 0; signature != NULL; signature = signature->next, signature_index++) { g_auto(gpgme_key_t) signature_key = NULL; (void) gpgme_get_key (result->context, signature->fpr, &signature_key, 0); if (signature_key == NULL) { g_debug ("Could not find key when looking up signature from %s.", signature->fpr); continue; } /* the first subkey in the list is the primary key */ if (!g_strcmp0 (lookup_key->subkeys->fpr, signature_key->subkeys->fpr)) { if (out_signature_index != NULL) *out_signature_index = signature_index; /* Note early return */ return TRUE; } } return FALSE; } /** * ostree_gpg_verify_result_get: * @result: an #OstreeGpgVerifyResult * @signature_index: which signature to get attributes from * @attrs: (array length=n_attrs): Array of requested attributes * @n_attrs: Length of the @attrs array * * Builds a #GVariant tuple of requested attributes for the GPG signature at * @signature_index in @result. See the #OstreeGpgSignatureAttr description * for the #GVariantType of each available attribute. * * It is a programmer error to request an invalid #OstreeGpgSignatureAttr or * an invalid @signature_index. Use ostree_gpg_verify_result_count_all() to * find the number of signatures in @result. * * Returns: a new, floating, #GVariant tuple **/ GVariant * ostree_gpg_verify_result_get (OstreeGpgVerifyResult *result, guint signature_index, OstreeGpgSignatureAttr *attrs, guint n_attrs) { GVariantBuilder builder; g_auto(gpgme_key_t) key = NULL; gpgme_signature_t signature; guint ii; g_return_val_if_fail (OSTREE_IS_GPG_VERIFY_RESULT (result), NULL); g_return_val_if_fail (attrs != NULL, NULL); g_return_val_if_fail (n_attrs > 0, NULL); signature = result->details->signatures; while (signature != NULL && signature_index > 0) { signature = signature->next; signature_index--; } g_return_val_if_fail (signature != NULL, NULL); /* Lookup the signing key if we need it. Note, failure to find * the key is not a fatal error. There's an attribute for that * (OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING). */ for (ii = 0; ii < n_attrs; ii++) { if (attrs[ii] == OSTREE_GPG_SIGNATURE_ATTR_USER_NAME || attrs[ii] == OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL || attrs[ii] == OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY || attrs[ii] == OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP || attrs[ii] == OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY) { (void) gpgme_get_key (result->context, signature->fpr, &key, 0); break; } } g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); for (ii = 0; ii < n_attrs; ii++) { GVariant *child; gboolean v_boolean; const char *v_string = NULL; gint64 v_int64; switch (attrs[ii]) { case OSTREE_GPG_SIGNATURE_ATTR_VALID: v_boolean = signature_is_valid (signature); child = g_variant_new_boolean (v_boolean); break; case OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED: v_boolean = ((signature->summary & GPGME_SIGSUM_SIG_EXPIRED) != 0); child = g_variant_new_boolean (v_boolean); break; case OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED: v_boolean = ((signature->summary & GPGME_SIGSUM_KEY_EXPIRED) != 0); child = g_variant_new_boolean (v_boolean); break; case OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED: v_boolean = signing_key_is_revoked (signature); child = g_variant_new_boolean (v_boolean); break; case OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING: v_boolean = ((signature->summary & GPGME_SIGSUM_KEY_MISSING) != 0); child = g_variant_new_boolean (v_boolean); break; case OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT: child = g_variant_new_string (signature->fpr); break; case OSTREE_GPG_SIGNATURE_ATTR_TIMESTAMP: child = g_variant_new_int64 ((gint64) signature->timestamp); break; case OSTREE_GPG_SIGNATURE_ATTR_EXP_TIMESTAMP: child = g_variant_new_int64 ((gint64) signature->exp_timestamp); break; case OSTREE_GPG_SIGNATURE_ATTR_PUBKEY_ALGO_NAME: v_string = gpgme_pubkey_algo_name (signature->pubkey_algo); if (v_string == NULL) v_string = "[unknown name]"; child = g_variant_new_string (v_string); break; case OSTREE_GPG_SIGNATURE_ATTR_HASH_ALGO_NAME: v_string = gpgme_hash_algo_name (signature->hash_algo); if (v_string == NULL) v_string = "[unknown name]"; child = g_variant_new_string (v_string); break; case OSTREE_GPG_SIGNATURE_ATTR_USER_NAME: if (key != NULL && key->uids != NULL) v_string = key->uids->name; if (v_string == NULL) v_string = "[unknown name]"; child = g_variant_new_string (v_string); break; case OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL: if (key != NULL && key->uids != NULL) v_string = key->uids->email; if (v_string == NULL) v_string = "[unknown email]"; child = g_variant_new_string (v_string); break; case OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY: if (key != NULL && key->subkeys != NULL) v_string = key->subkeys->fpr; if (v_string == NULL) v_string = ""; child = g_variant_new_string (v_string); break; case OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP: v_int64 = 0; if (key != NULL) { gpgme_subkey_t subkey = key->subkeys; while (subkey != NULL && (g_strcmp0 (subkey->fpr, signature->fpr) != 0)) subkey = subkey->next; if (subkey != NULL) v_int64 = subkey->expires; } child = g_variant_new_int64 (v_int64); break; case OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY: if (key != NULL && key->subkeys != NULL) v_int64 = key->subkeys->expires; else v_int64 = 0; child = g_variant_new_int64 (v_int64); break; default: g_critical ("Invalid signature attribute (%d)", attrs[ii]); g_variant_builder_clear (&builder); return NULL; } g_variant_builder_add_value (&builder, child); } return g_variant_builder_end (&builder); } /** * ostree_gpg_verify_result_get_all: * @result: an #OstreeGpgVerifyResult * @signature_index: which signature to get attributes from * * Builds a #GVariant tuple of all available attributes for the GPG signature * at @signature_index in @result. * * The child values in the returned #GVariant tuple are ordered to match the * #OstreeGpgSignatureAttr enumeration, which means the enum values can be * used as index values in functions like g_variant_get_child(). See the * #OstreeGpgSignatureAttr description for the #GVariantType of each * available attribute. * * * * The #OstreeGpgSignatureAttr enumeration may be extended in the future * with new attributes, which would affect the #GVariant tuple returned by * this function. While the position and type of current child values in * the #GVariant tuple will not change, to avoid backward-compatibility * issues please do not depend on the tuple's overall size or * type signature. * * * * It is a programmer error to request an invalid @signature_index. Use * ostree_gpg_verify_result_count_all() to find the number of signatures in * @result. * * Returns: a new, floating, #GVariant tuple **/ GVariant * ostree_gpg_verify_result_get_all (OstreeGpgVerifyResult *result, guint signature_index) { g_return_val_if_fail (OSTREE_IS_GPG_VERIFY_RESULT (result), NULL); return ostree_gpg_verify_result_get (result, signature_index, all_signature_attrs, G_N_ELEMENTS (all_signature_attrs)); } /** * ostree_gpg_verify_result_describe: * @result: an #OstreeGpgVerifyResult * @signature_index: which signature to describe * @output_buffer: a #GString to hold the description * @line_prefix: (allow-none): optional line prefix string * @flags: flags to adjust the description format * * Appends a brief, human-readable description of the GPG signature at * @signature_index in @result to the @output_buffer. The description * spans multiple lines. A @line_prefix string, if given, will precede * each line of the description. * * The @flags argument is reserved for future variations to the description * format. Currently must be 0. * * It is a programmer error to request an invalid @signature_index. Use * ostree_gpg_verify_result_count_all() to find the number of signatures in * @result. */ void ostree_gpg_verify_result_describe (OstreeGpgVerifyResult *result, guint signature_index, GString *output_buffer, const gchar *line_prefix, OstreeGpgSignatureFormatFlags flags) { g_autoptr(GVariant) variant = NULL; g_return_if_fail (OSTREE_IS_GPG_VERIFY_RESULT (result)); variant = ostree_gpg_verify_result_get_all (result, signature_index); ostree_gpg_verify_result_describe_variant (variant, output_buffer, line_prefix, flags); } static void append_expire_info (GString *output_buffer, const gchar *line_prefix, const gchar *exp_type, gint64 exp_timestamp, gboolean expired) { if (line_prefix != NULL) g_string_append (output_buffer, line_prefix); g_autoptr(GDateTime) date_time_utc = g_date_time_new_from_unix_utc (exp_timestamp); if (date_time_utc == NULL) { g_string_append_printf (output_buffer, "%s expiry timestamp (%" G_GINT64_FORMAT ") is invalid\n", exp_type, exp_timestamp); return; } g_autoptr(GDateTime) date_time_local = g_date_time_to_local (date_time_utc); g_autofree char *formatted_date_time = g_date_time_format (date_time_local, "%c"); if (expired) { g_string_append_printf (output_buffer, "%s expired %s\n", exp_type, formatted_date_time); } else { g_string_append_printf (output_buffer, "%s expires %s\n", exp_type, formatted_date_time); } } /** * ostree_gpg_verify_result_describe_variant: * @variant: a #GVariant from ostree_gpg_verify_result_get_all() * @output_buffer: a #GString to hold the description * @line_prefix: (allow-none): optional line prefix string * @flags: flags to adjust the description format * * Similar to ostree_gpg_verify_result_describe() but takes a #GVariant of * all attributes for a GPG signature instead of an #OstreeGpgVerifyResult * and signature index. * * The @variant MUST have been created by * ostree_gpg_verify_result_get_all(). */ void ostree_gpg_verify_result_describe_variant (GVariant *variant, GString *output_buffer, const gchar *line_prefix, OstreeGpgSignatureFormatFlags flags) { g_autoptr(GDateTime) date_time_utc = NULL; g_autoptr(GDateTime) date_time_local = NULL; g_autofree char *formatted_date_time = NULL; gint64 timestamp; gint64 exp_timestamp; gint64 key_exp_timestamp; gint64 key_exp_timestamp_primary; const char *type_string; const char *fingerprint; const char *fingerprint_primary; const char *pubkey_algo; const char *user_name; const char *user_email; const char *key_id; gboolean valid; gboolean sig_expired; gboolean key_expired; gboolean key_revoked; gboolean key_missing; gsize len; g_return_if_fail (variant != NULL); g_return_if_fail (output_buffer != NULL); /* Verify the variant's type string. This code is * not prepared to handle just any random GVariant. */ type_string = g_variant_get_type_string (variant); g_return_if_fail (strcmp (type_string, "(bbbbbsxxsssssxx)") == 0); /* The default format roughly mimics the verify output generated by * check_sig_and_print() in gnupg/g10/mainproc.c, though obviously * greatly simplified. */ g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_VALID, "b", &valid); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED, "b", &sig_expired); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED, "b", &key_expired); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED, "b", &key_revoked); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING, "b", &key_missing); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT, "&s", &fingerprint); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY, "&s", &fingerprint_primary); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_TIMESTAMP, "x", ×tamp); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_EXP_TIMESTAMP, "x", &exp_timestamp); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_PUBKEY_ALGO_NAME, "&s", &pubkey_algo); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_USER_NAME, "&s", &user_name); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL, "&s", &user_email); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP, "x", &key_exp_timestamp); g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY, "x", &key_exp_timestamp_primary); len = strlen (fingerprint); key_id = (len > 16) ? fingerprint + len - 16 : fingerprint; date_time_utc = g_date_time_new_from_unix_utc (timestamp); if (date_time_utc == NULL) { g_string_append_printf (output_buffer, "Can't check signature: timestamp %" G_GINT64_FORMAT " is invalid\n", timestamp); return; } date_time_local = g_date_time_to_local (date_time_utc); formatted_date_time = g_date_time_format (date_time_local, "%c"); if (line_prefix != NULL) g_string_append (output_buffer, line_prefix); g_string_append_printf (output_buffer, "Signature made %s using %s key ID %s\n", formatted_date_time, pubkey_algo, key_id); g_clear_pointer (&date_time_utc, g_date_time_unref); g_clear_pointer (&date_time_local, g_date_time_unref); g_clear_pointer (&formatted_date_time, g_free); if (line_prefix != NULL) g_string_append (output_buffer, line_prefix); if (key_missing) { g_string_append (output_buffer, "Can't check signature: public key not found\n"); } else if (valid) { g_string_append_printf (output_buffer, "Good signature from \"%s <%s>\"\n", user_name, user_email); } else if (key_revoked) { g_string_append (output_buffer, "Key revoked\n"); } else if (sig_expired) { g_string_append_printf (output_buffer, "Expired signature from \"%s <%s>\"\n", user_name, user_email); } else { g_string_append_printf (output_buffer, "BAD signature from \"%s <%s>\"\n", user_name, user_email); } if (!key_missing && (g_strcmp0 (fingerprint, fingerprint_primary) != 0)) { const char *key_id_primary; len = strlen (fingerprint_primary); key_id_primary = (len > 16) ? fingerprint_primary + len - 16 : fingerprint_primary; if (line_prefix != NULL) g_string_append (output_buffer, line_prefix); g_string_append_printf (output_buffer, "Primary key ID %s\n", key_id_primary); } if (exp_timestamp > 0) append_expire_info (output_buffer, line_prefix, "Signature", exp_timestamp, sig_expired); if (key_exp_timestamp > 0) append_expire_info (output_buffer, line_prefix, "Key", key_exp_timestamp, key_expired); if (key_exp_timestamp_primary > 0 && (g_strcmp0 (fingerprint, fingerprint_primary) != 0)) append_expire_info (output_buffer, line_prefix, "Primary key", key_exp_timestamp_primary, key_expired); } /** * ostree_gpg_verify_result_require_valid_signature: * @result: (nullable): an #OstreeGpgVerifyResult * @error: A #GError * * Checks if the result contains at least one signature from the * trusted keyring. You can call this function immediately after * ostree_repo_verify_summary() or ostree_repo_verify_commit_ext() - * it will handle the %NULL @result and filled @error too. * * Returns: %TRUE if @result was not %NULL and had at least one * signature from trusted keyring, otherwise %FALSE * * Since: 2016.6 */ gboolean ostree_gpg_verify_result_require_valid_signature (OstreeGpgVerifyResult *result, GError **error) { if (result == NULL) return FALSE; if (ostree_gpg_verify_result_count_valid (result) == 0) { /* * Join the description of each failed signature for the error message. * Only one error code can be returned, so if there was more than one * signature, use the error of the last one under the assumption that * it's the most recent and hopefully most likely to be made with a * valid key. */ gint code = OSTREE_GPG_ERROR_NO_SIGNATURE; g_autoptr(GString) buffer = g_string_sized_new (256); guint nsigs = ostree_gpg_verify_result_count_all (result); if (nsigs == 0) /* In case an empty result was passed in */ g_string_append (buffer, "No GPG signatures found"); else { for (int i = nsigs - 1; i >= 0; i--) { g_autoptr(GVariant) info = ostree_gpg_verify_result_get_all (result, i); ostree_gpg_verify_result_describe_variant (info, buffer, "", OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT); if (i == nsigs - 1) { gboolean key_missing, key_revoked, key_expired, sig_expired; g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING, "b", &key_missing); g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED, "b", &key_revoked); g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED, "b", &key_expired); g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED, "b", &sig_expired); if (key_missing) code = OSTREE_GPG_ERROR_MISSING_KEY; else if (key_revoked) code = OSTREE_GPG_ERROR_REVOKED_KEY; else if (key_expired) code = OSTREE_GPG_ERROR_EXPIRED_KEY; else if (sig_expired) code = OSTREE_GPG_ERROR_EXPIRED_SIGNATURE; else /* Assume any other issue is a bad signature */ code = OSTREE_GPG_ERROR_INVALID_SIGNATURE; } } } /* Strip any trailing newlines */ g_strchomp (buffer->str); g_set_error_literal (error, OSTREE_GPG_ERROR, code, buffer->str); return FALSE; } return TRUE; } G_DEFINE_QUARK (OstreeGpgError, ostree_gpg_error)