git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Christian Couder <christian.couder@gmail.com>
To: git@vger.kernel.org
Cc: Junio C Hamano <gitster@pobox.com>,
	Patrick Steinhardt <ps@pks.im>, Elijah Newren <newren@gmail.com>,
	Jeff King <peff@peff.net>,
	"brian m . carlson" <sandals@crustytoothpaste.net>,
	Johannes Schindelin <Johannes.Schindelin@gmx.de>,
	Christian Couder <christian.couder@gmail.com>,
	Christian Couder <chriscool@tuxfamily.org>
Subject: [PATCH v2 4/6] gpg-interface: extract hash algorithm from signature status output
Date: Mon, 26 May 2025 12:33:12 +0200	[thread overview]
Message-ID: <20250526103314.1542316-5-christian.couder@gmail.com> (raw)
In-Reply-To: <20250526103314.1542316-1-christian.couder@gmail.com>

When using GPG/GPGSM to verify OpenPGP/X.509 signatures, the
verification result (good/bad/etc.), signer, and key fingerprint are
extracted from the output, but not the specific hash algorithm (e.g.,
"sha1", "sha256") reported by GPG as having been used for the
signature itself.

Let's improve the `gpg-interface` parsing logic to capture this
information.

This information can be useful for Git commands or external tools
that process signature information. For example, it could be used
when displaying signature verification results to users or when
working with various signature formats in tools like fast-export and
fast-import.

GPG provides the hash algorithm ID used for the signature within
its machine-readable status output, specifically in the fields
following the `VALIDSIG` and `ERRSIG` keywords, as documented in
GnuPG's `doc/DETAILS`.

The implementation follows RFC 4880 (OpenPGP Message Format) section
9.4 for the mapping between hash algorithm IDs and their
corresponding names.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 gpg-interface.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++
 gpg-interface.h |  4 +++
 2 files changed, 78 insertions(+)

diff --git a/gpg-interface.c b/gpg-interface.c
index e7af82d123..15687ede43 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -153,6 +153,7 @@ void signature_check_clear(struct signature_check *sigc)
 	FREE_AND_NULL(sigc->key);
 	FREE_AND_NULL(sigc->fingerprint);
 	FREE_AND_NULL(sigc->primary_key_fingerprint);
+	FREE_AND_NULL(sigc->sig_algo);
 }
 
 /* An exclusive status -- only one of them can appear in output */
@@ -221,6 +222,65 @@ static int parse_gpg_trust_level(const char *level,
 	return 1;
 }
 
+/* See RFC 4880: OpenPGP Message Format, section 9.4. Hash Algorithms */
+static struct sigcheck_gpg_hash_algo {
+	const char *id;
+	const char *name;
+} sigcheck_gpg_hash_algo[] = {
+	{ "1", "md5" },       /* deprecated */
+	{ "2", "sha1" },      /* mandatory */
+	{ "3", "ripemd160" },
+	{ "8", "sha256" },
+	{ "9", "sha384" },
+	{ "10", "sha512" },
+	{ "11", "sha224" },
+};
+
+static const char *lookup_gpg_hash_algo(const char *algo_id)
+{
+	if (!algo_id)
+		return NULL;
+
+	for (size_t i = 0; i < ARRAY_SIZE(sigcheck_gpg_hash_algo); i++) {
+		if (!strcmp(sigcheck_gpg_hash_algo[i].id, algo_id))
+			return sigcheck_gpg_hash_algo[i].name;
+	}
+
+	return NULL;
+}
+
+static char *extract_gpg_hash_algo(const char *args_start,
+				   const char *line_end,
+				   int field_index)
+{
+	const char *p = args_start;
+	int current_field = 0;
+	char *result = NULL;
+
+	while (p < line_end && current_field < field_index) {
+		/* Skip to the end of the current field */
+		while (p < line_end && *p != ' ')
+			p++;
+		/* Skip spaces to get to the start of the next field */
+		while (p < line_end && *p == ' ') {
+			p++;
+			current_field++;
+		}
+	}
+
+	if (p < line_end && current_field == field_index) {
+		/* Found start of the target field */
+		const char *algo_id_end = strchrnul(p, ' ');
+		char *algo_id = xmemdupz(p, algo_id_end - p);
+		const char *hash_algo = lookup_gpg_hash_algo(algo_id);
+		if (hash_algo)
+			result = xstrdup(hash_algo);
+		free(algo_id);
+	}
+
+	return result;
+}
+
 static void parse_gpg_output(struct signature_check *sigc)
 {
 	const char *buf = sigc->gpg_status;
@@ -242,6 +302,18 @@ static void parse_gpg_output(struct signature_check *sigc)
 		/* Iterate over all search strings */
 		for (size_t i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
 			if (skip_prefix(line, sigcheck_gpg_status[i].check, &line)) {
+
+				/* Do we have hash algorithm? */
+				if (!sigc->sig_algo) {
+					const char *line_end = strchrnul(line, '\n');
+					if (!strcmp(sigcheck_gpg_status[i].check, "VALIDSIG "))
+						/* Hash algorithm is the 8th field in VALIDSIG */
+						sigc->sig_algo = extract_gpg_hash_algo(line, line_end, 7);
+					else if (!strcmp(sigcheck_gpg_status[i].check, "ERRSIG "))
+						/* Hash algorithm is the 3rd field in ERRSIG */
+						sigc->sig_algo = extract_gpg_hash_algo(line, line_end, 2);
+				}
+
 				/*
 				 * GOODSIG, BADSIG etc. can occur only once for
 				 * each signature.  Therefore, if we had more
@@ -323,6 +395,7 @@ static void parse_gpg_output(struct signature_check *sigc)
 			}
 		}
 	}
+
 	return;
 
 error:
@@ -332,6 +405,7 @@ static void parse_gpg_output(struct signature_check *sigc)
 	FREE_AND_NULL(sigc->fingerprint);
 	FREE_AND_NULL(sigc->signer);
 	FREE_AND_NULL(sigc->key);
+	FREE_AND_NULL(sigc->sig_algo);
 }
 
 static int verify_gpg_signed_buffer(struct signature_check *sigc,
diff --git a/gpg-interface.h b/gpg-interface.h
index 9a32dd6ce8..2b7701ca2c 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -42,6 +42,10 @@ struct signature_check {
 	char *key;
 	char *fingerprint;
 	char *primary_key_fingerprint;
+
+	/* hash algo for GPG/GPGSM, key type for SSH */
+	char *sig_algo;
+
 	enum signature_trust_level trust_level;
 };
 
-- 
2.49.0.609.g63c55177e5


  parent reply	other threads:[~2025-05-26 10:33 UTC|newest]

Thread overview: 65+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-24 20:39 [PATCH] fast-(import|export): improve on the signature algorithm name Christian Couder
2025-04-24 21:19 ` Junio C Hamano
2025-04-24 21:59   ` Elijah Newren
2025-04-24 22:58     ` Junio C Hamano
2025-05-26 10:35       ` Christian Couder
2025-05-27 15:18         ` Junio C Hamano
2025-05-28 17:29           ` Junio C Hamano
2025-05-28 20:06             ` Elijah Newren
2025-05-28 21:59               ` Junio C Hamano
2025-05-28 23:15                 ` Elijah Newren
2025-05-29  3:14                   ` Junio C Hamano
2025-06-02 15:56                     ` Christian Couder
2025-06-02 15:56             ` Christian Couder
2025-06-02 16:20               ` Junio C Hamano
2025-05-26 10:34   ` Christian Couder
2025-04-24 21:41 ` Elijah Newren
2025-05-26 10:34   ` Christian Couder
2025-04-24 22:05 ` brian m. carlson
2025-05-26 10:35   ` Christian Couder
2025-04-24 23:25 ` Junio C Hamano
2025-05-26 10:33 ` [PATCH v2 0/6] extract algo information from signatures Christian Couder
2025-05-26 10:33   ` [PATCH v2 1/6] gpg-interface: simplify ssh fingerprint parsing Christian Couder
2025-05-26 10:33   ` [PATCH v2 2/6] gpg-interface: use left shift to define GPG_VERIFY_* Christian Couder
2025-05-26 10:33   ` [PATCH v2 3/6] doc/verify-commit: update and improve the whole doc Christian Couder
2025-05-26 10:33   ` Christian Couder [this message]
2025-05-26 10:33   ` [PATCH v2 5/6] gpg-interface: extract SSH key type from signature status output Christian Couder
2025-05-26 10:33   ` [PATCH v2 6/6] verify-commit: add a --summary flag Christian Couder
2025-05-26 16:03   ` [PATCH v2 0/6] extract algo information from signatures Elijah Newren
2025-06-19 13:38     ` Christian Couder
2025-06-02 22:17   ` brian m. carlson
2025-06-19 13:37     ` Christian Couder
2025-06-18 15:18   ` [PATCH v3] fast-(import|export): improve on commit signature output format Christian Couder
2025-06-19 13:36     ` [PATCH v4] " Christian Couder
2025-06-19 14:55       ` Junio C Hamano
2025-07-08  9:16         ` Christian Couder
2025-06-19 21:44       ` Elijah Newren
2025-06-20 16:12         ` Christian Couder
2025-06-20 19:20           ` Junio C Hamano
2025-07-08  9:16             ` Christian Couder
2025-06-26 19:11           ` Elijah Newren
2025-07-08  9:16             ` Christian Couder
2025-07-07 22:58       ` Junio C Hamano
2025-07-08  3:35         ` Christian Couder
2025-07-08  5:03           ` Junio C Hamano
2025-07-08  6:38             ` Patrick Steinhardt
2025-07-08 11:08               ` Christian Couder
2025-07-08 16:38                 ` Junio C Hamano
2025-07-09  0:19                   ` Christian Couder
2025-07-09 15:35                     ` Junio C Hamano
2025-07-10  8:25                     ` Patrick Steinhardt
2025-07-10 15:29                       ` Christian Couder
2025-07-10 15:33                       ` Junio C Hamano
2025-07-08 10:17             ` Christian Couder
2025-07-08  9:17       ` [PATCH v5] " Christian Couder
2025-07-08 21:58         ` Junio C Hamano
2025-07-08 23:08         ` Elijah Newren
2025-07-09  0:03           ` Junio C Hamano
2025-07-09  0:10             ` Elijah Newren
2025-07-09 10:18             ` Christian Couder
2025-07-09 10:15           ` Christian Couder
2025-07-09 14:12         ` [PATCH v6] " Christian Couder
2025-07-09 23:14           ` Junio C Hamano
2025-07-14 21:07           ` Elijah Newren
2025-07-14 21:23             ` Junio C Hamano
2025-07-25 16:11               ` Christian Couder

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250526103314.1542316-5-christian.couder@gmail.com \
    --to=christian.couder@gmail.com \
    --cc=Johannes.Schindelin@gmx.de \
    --cc=chriscool@tuxfamily.org \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=newren@gmail.com \
    --cc=peff@peff.net \
    --cc=ps@pks.im \
    --cc=sandals@crustytoothpaste.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).