git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/1] Limit search for primary key fingerprint
@ 2019-11-16 18:06 Hans Jerry Illikainen
  2019-11-16 18:06 ` [PATCH 1/1] gpg-interface: limit " Hans Jerry Illikainen
  2019-11-16 19:49 ` [PATCH 0/1] Limit " Jonathan Nieder
  0 siblings, 2 replies; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-16 18:06 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

As part of implementing signature verification for git clone, I decided
to refactor/unify the code for commit and merge verification to make it
reusable during clones.

This lead me to discover that git requires merge signatures to be
trusted (as opposed to TRUST_UNKNOWN or TRUST_NEVER).  This is unlike
the behavior of verify-tag and verify-commit.

So, I figured that I'd make the minimum trust level configurable to make
the behavior of merge/commit/tag consistent.  And while doing so, I
noticed that parse_gpg_output() in gpg-interface.c assumes that the
VALIDSIG status line has a field with a fingerprint for the primary key;
but that is only the case for OpenPGP signatures [1].

The consequence of that assumption is that the subsequent status line is
interpreted as the primary fingerprint for X509 signatures.  I'm not
sure if the order is hardcoded in GnuPG, but in my testing the TRUST_
status line always came after VALIDSIG -- and that breaks the config
option to set a minimum trust level (not part of this patch):

,----
| $ git log -n1 --format="primary key: %GP" signed-x509
| gpgsm: Signature made 2019-11-16 14:13:09 using certificate ID 0xFA23FD65
| gpgsm: Good signature from "/CN=C O Mitter/O=Example/SN=C O/GN=Mitter"
| gpgsm:                 aka "committer@example.com"
| primary key: TRUST_FULLY 0 shell
`----

[1]: https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS


Hans Jerry Illikainen (1):
  gpg-interface: limit search for primary key fingerprint

 gpg-interface.c | 20 +++++++++++++++-----
 t/t4202-log.sh  |  6 ++++++
 2 files changed, 21 insertions(+), 5 deletions(-)

--
2.24.0.156.g69483321b9.dirty

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-16 18:06 [PATCH 0/1] Limit search for primary key fingerprint Hans Jerry Illikainen
@ 2019-11-16 18:06 ` Hans Jerry Illikainen
  2019-11-18  5:40   ` Junio C Hamano
  2019-11-16 19:49 ` [PATCH 0/1] Limit " Jonathan Nieder
  1 sibling, 1 reply; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-16 18:06 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

The VALIDSIG status line from GnuPG with --status-fd has a field that
specifies the fingerprint of the primary key that made the signature.
However, that field is only available for OpenPGP signatures; not for
CMS/X.509.

An unbounded search for a non-existent primary key fingerprint for X509
signatures results in the following status line being interpreted as the
fingerprint.

Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
---
 gpg-interface.c | 20 +++++++++++++++-----
 t/t4202-log.sh  |  6 ++++++
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index d60115ca40..01c7ef42d4 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -148,21 +148,31 @@ static void parse_gpg_output(struct signature_check *sigc)
 				}
 				/* Do we have fingerprint? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
+					const char *limit;
+
 					next = strchrnul(line, ' ');
 					free(sigc->fingerprint);
 					sigc->fingerprint = xmemdupz(line, next - line);
 
-					/* Skip interim fields */
+					/* Skip interim fields.  The search is
+					 * limited to the same line since only
+					 * OpenPGP signatures has a field with
+					 * the primary fingerprint. */
+					limit = strchrnul(line, '\n');
 					for (j = 9; j > 0; j--) {
-						if (!*next)
+						if (!*next || next >= limit)
 							break;
 						line = next + 1;
 						next = strchrnul(line, ' ');
 					}
 
-					next = strchrnul(line, '\n');
-					free(sigc->primary_key_fingerprint);
-					sigc->primary_key_fingerprint = xmemdupz(line, next - line);
+					if (j == 0) {
+						next = strchrnul(line, '\n');
+						free(sigc->primary_key_fingerprint);
+						sigc->primary_key_fingerprint =
+							xmemdupz(line,
+								 next - line);
+					}
 				}
 
 				break;
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index e803ba402e..5d893b3137 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -1580,6 +1580,12 @@ test_expect_success GPGSM 'setup signed branch x509' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPGSM 'log x509 fingerprint' '
+	echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
+	git log -n1 --format="%GF | %GP" signed-x509 >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success GPG 'log --graph --show-signature' '
 	git log --graph --show-signature -n1 signed >actual &&
 	grep "^| gpg: Signature made" actual &&
-- 
2.24.0.156.g69483321b9.dirty


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 0/1] Limit search for primary key fingerprint
  2019-11-16 18:06 [PATCH 0/1] Limit search for primary key fingerprint Hans Jerry Illikainen
  2019-11-16 18:06 ` [PATCH 1/1] gpg-interface: limit " Hans Jerry Illikainen
@ 2019-11-16 19:49 ` Jonathan Nieder
  2019-11-16 21:58   ` [PATCH v2 " Hans Jerry Illikainen
  2019-11-18  4:45   ` [PATCH 0/1] Limit search for primary key fingerprint Junio C Hamano
  1 sibling, 2 replies; 21+ messages in thread
From: Jonathan Nieder @ 2019-11-16 19:49 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Hi,

Hans Jerry Illikainen wrote:

> As part of implementing signature verification for git clone, I decided
> to refactor/unify the code for commit and merge verification to make it
> reusable during clones.

Thanks for writing this.

Most of the text in this cover letter would be useful to have in the
commit message.  From the commit message alone, I could see that you
were fixing a bug, but I could not see the motivation or workflow it
is part of.  If I were to later discover an issue triggered by this
commit, I wouldn't have enough information to weigh tradeoffs about
the right way to address such an issue.

Thanks and hope that helps,
Jonathan

> This lead me to discover that git requires merge signatures to be
> trusted (as opposed to TRUST_UNKNOWN or TRUST_NEVER).  This is unlike
> the behavior of verify-tag and verify-commit.
>
> So, I figured that I'd make the minimum trust level configurable to make
> the behavior of merge/commit/tag consistent.  And while doing so, I
> noticed that parse_gpg_output() in gpg-interface.c assumes that the
> VALIDSIG status line has a field with a fingerprint for the primary key;
> but that is only the case for OpenPGP signatures [1].
>
> The consequence of that assumption is that the subsequent status line is
> interpreted as the primary fingerprint for X509 signatures.  I'm not
> sure if the order is hardcoded in GnuPG, but in my testing the TRUST_
> status line always came after VALIDSIG -- and that breaks the config
> option to set a minimum trust level (not part of this patch):
>
> ,----
> | $ git log -n1 --format="primary key: %GP" signed-x509
> | gpgsm: Signature made 2019-11-16 14:13:09 using certificate ID 0xFA23FD65
> | gpgsm: Good signature from "/CN=C O Mitter/O=Example/SN=C O/GN=Mitter"
> | gpgsm:                 aka "committer@example.com"
> | primary key: TRUST_FULLY 0 shell
> `----
>
> [1]: https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 0/1] Limit search for primary key fingerprint
  2019-11-16 19:49 ` [PATCH 0/1] Limit " Jonathan Nieder
@ 2019-11-16 21:58   ` Hans Jerry Illikainen
  2019-11-16 21:58     ` [PATCH v2 1/1] gpg-interface: limit " Hans Jerry Illikainen
  2019-11-21 23:43     ` [PATCH v3 0/2] gpg-interface: fix " Hans Jerry Illikainen
  2019-11-18  4:45   ` [PATCH 0/1] Limit search for primary key fingerprint Junio C Hamano
  1 sibling, 2 replies; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-16 21:58 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

As part of implementing signature verification for git clone, I decided
to refactor/unify the code for commit and merge verification to make it
reusable during clones.

This lead me to discover that git requires merge signatures to be
trusted (as opposed to TRUST_UNKNOWN or TRUST_NEVER).  This is unlike
the behavior of verify-tag and verify-commit.

So, I figured that I'd make the minimum trust level configurable to make
the behavior of merge/commit/tag consistent.  And while doing so, I
noticed that parse_gpg_output() in gpg-interface.c assumes that the
VALIDSIG status line has a field with a fingerprint for the primary key;
but that is only the case for OpenPGP signatures [1].

The consequence of that assumption is that the subsequent status line is
interpreted as the primary fingerprint for X509 signatures.  I'm not
sure if the order is hardcoded in GnuPG, but in my testing the TRUST_
status line always came after VALIDSIG -- and that breaks the config
option to set a minimum trust level (not part of this patch):

,----
| $ git log -n1 --format="primary key: %GP" signed-x509
| gpgsm: Signature made 2019-11-16 14:13:09 using certificate ID 0xFA23FD65
| gpgsm: Good signature from "/CN=C O Mitter/O=Example/SN=C O/GN=Mitter"
| gpgsm:                 aka "committer@example.com"
| primary key: TRUST_FULLY 0 shell
`----

[1]: https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS


Hans Jerry Illikainen (1):
  gpg-interface: limit search for primary key fingerprint

 gpg-interface.c | 20 +++++++++++++++-----
 t/t4202-log.sh  |  6 ++++++
 2 files changed, 21 insertions(+), 5 deletions(-)

--
2.24.GIT

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-16 21:58   ` [PATCH v2 " Hans Jerry Illikainen
@ 2019-11-16 21:58     ` Hans Jerry Illikainen
  2019-11-21 23:43     ` [PATCH v3 0/2] gpg-interface: fix " Hans Jerry Illikainen
  1 sibling, 0 replies; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-16 21:58 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

As part of implementing signature verification for git clone, I decided
to refactor/unify the code for commit and merge verification to make it
reusable during clones.

This lead me to discover that git requires merge signatures to be
trusted (as opposed to TRUST_UNKNOWN or TRUST_NEVER).  This is unlike
the behavior of verify-tag and verify-commit.

So, I figured that I'd make the minimum trust level configurable to make
the behavior of merge/commit/tag consistent.  And while doing so, I
noticed that parse_gpg_output() in gpg-interface.c assumes that the
VALIDSIG status line has a field with a fingerprint for the primary key;
but that is only the case for OpenPGP signatures.

The consequence of that assumption is that the subsequent status line is
interpreted as the primary fingerprint for X509 signatures.  I'm not
sure if the order is hardcoded in GnuPG, but in my testing the TRUST_
status line always came after VALIDSIG -- and that breaks the
possibility of a configuration option to set a minimum trust level since
the TRUST_ line is consumed by VALIDSIG.

Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
---
 gpg-interface.c | 20 +++++++++++++++-----
 t/t4202-log.sh  |  6 ++++++
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index d60115ca40..01c7ef42d4 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -148,21 +148,31 @@ static void parse_gpg_output(struct signature_check *sigc)
 				}
 				/* Do we have fingerprint? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
+					const char *limit;
+
 					next = strchrnul(line, ' ');
 					free(sigc->fingerprint);
 					sigc->fingerprint = xmemdupz(line, next - line);
 
-					/* Skip interim fields */
+					/* Skip interim fields.  The search is
+					 * limited to the same line since only
+					 * OpenPGP signatures has a field with
+					 * the primary fingerprint. */
+					limit = strchrnul(line, '\n');
 					for (j = 9; j > 0; j--) {
-						if (!*next)
+						if (!*next || next >= limit)
 							break;
 						line = next + 1;
 						next = strchrnul(line, ' ');
 					}
 
-					next = strchrnul(line, '\n');
-					free(sigc->primary_key_fingerprint);
-					sigc->primary_key_fingerprint = xmemdupz(line, next - line);
+					if (j == 0) {
+						next = strchrnul(line, '\n');
+						free(sigc->primary_key_fingerprint);
+						sigc->primary_key_fingerprint =
+							xmemdupz(line,
+								 next - line);
+					}
 				}
 
 				break;
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index e803ba402e..5d893b3137 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -1580,6 +1580,12 @@ test_expect_success GPGSM 'setup signed branch x509' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPGSM 'log x509 fingerprint' '
+	echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
+	git log -n1 --format="%GF | %GP" signed-x509 >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success GPG 'log --graph --show-signature' '
 	git log --graph --show-signature -n1 signed >actual &&
 	grep "^| gpg: Signature made" actual &&
-- 
2.24.GIT


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 0/1] Limit search for primary key fingerprint
  2019-11-16 19:49 ` [PATCH 0/1] Limit " Jonathan Nieder
  2019-11-16 21:58   ` [PATCH v2 " Hans Jerry Illikainen
@ 2019-11-18  4:45   ` Junio C Hamano
  1 sibling, 0 replies; 21+ messages in thread
From: Junio C Hamano @ 2019-11-18  4:45 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Hans Jerry Illikainen, git

Jonathan Nieder <jrnieder@gmail.com> writes:

> Hans Jerry Illikainen wrote:
>
>> As part of implementing signature verification for git clone, I decided
>> to refactor/unify the code for commit and merge verification to make it
>> reusable during clones.
>
> Thanks for writing this.
>
> Most of the text in this cover letter would be useful to have in the
> commit message.  From the commit message alone, I could see that you
> were fixing a bug, but I could not see the motivation or workflow it
> is part of.  If I were to later discover an issue triggered by this
> commit, I wouldn't have enough information to weigh tradeoffs about
> the right way to address such an issue.

After reading the proposed log message of [PATCH v1 1/1], I have to
disagree.  It does not matter if we will later see new code in the
clone codepath that would use the gpg-interface API.  Whether it
happens or not, this change to look for the key fingerprint only on
the same line is something we should consider independently.

On the other hand, why the author of this change thought that it may
be necessary thing to do is an excellent material to tell the story
behind the patch in the cover letter.

Thanks.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-16 18:06 ` [PATCH 1/1] gpg-interface: limit " Hans Jerry Illikainen
@ 2019-11-18  5:40   ` Junio C Hamano
  2019-11-21 23:19     ` Hans Jerry Illikainen
  0 siblings, 1 reply; 21+ messages in thread
From: Junio C Hamano @ 2019-11-18  5:40 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Hans Jerry Illikainen <hji@dyntopia.com> writes:

> The VALIDSIG status line from GnuPG with --status-fd has a field that
> specifies the fingerprint of the primary key that made the signature.
> However, that field is only available for OpenPGP signatures; not for
> CMS/X.509.
>
> An unbounded search for a non-existent primary key fingerprint for X509
> signatures results in the following status line being interpreted as the
> fingerprint.

The above two paragraphs give us an excellent explanation of what
happens in today's code.  What needs to follow is a description of
the approach taken to solve the problem in such a way that helps
readers to agree or disagree with the patch.  It needs to convince
them of at least two things:

 - The change is necessary to avoid a wrong line from getting
   mistaken as the fingerprint with CMS/X.509 sigs, and instead we
   say "there is no fingerprint" on X.509 sig (or whatever happens
   with this change---I cannot tell what ramifications lack of the
   fingerprint has to the callers of this function offhand, or how
   the lack of fingerprint is reported back to the callers, but the
   proposed log message must talk about it).

 - The change safely keeps identifying the right fingerprint with
   OpenPGP sigs because the primary fingerprint, if shown, must be
   on the same line (or whatever reason why it makes it safe---I do
   not offhand know if this is guaranteed how and by whom [*1*], but
   you must have researched it before sending this patch, so please
   write it down to help readers).

>  				/* Do we have fingerprint? */
>  				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
> +					const char *limit;
> +

I wonder if it is simpler to define it next to 'next'.  Yes, this
new variable is used only within this block, but it also gets used
only in conjunction with that other variable.

>  					next = strchrnul(line, ' ');
>  					free(sigc->fingerprint);
>  					sigc->fingerprint = xmemdupz(line, next - line);

So, we skipped "VALIDSIG " and grabbed the first field <fingerprint>
in sigc->fingerprint.  Then we used to unconditionally skip 9 SP
separated fields.  But there may only be 8 fields on the line, which
is why this patch is needed.

> -					/* Skip interim fields */
> +					/* Skip interim fields.  The search is
> +					 * limited to the same line since only
> +					 * OpenPGP signatures has a field with
> +					 * the primary fingerprint. */

	/*
	 * A multi-line comment of ours looks like this; the
	 * slash-asterisk that begins it, and the asterisk-slash
	 * that ends it, are on their own lines, without anything
	 * else but the indentation.
	 */

> +					limit = strchrnul(line, '\n');
>  					for (j = 9; j > 0; j--) {
> -						if (!*next)
> +						if (!*next || next >= limit)
>  							break;

This makes sure that a premature exit (i.e. "0 < j") means we ran
out of the fields.  

I'd prefer to write it "limit <= next" to help visualizing the two
values (on a single line, lower values come left, higher ones come
right), by the way.  That is a minor point.

A bigger question is, when this happens, what value do we want to
leave in sigc->primary_key_fingerprint?  As we can see from the
original code that makes sure the old value in the field will not
leak by first free()ing, it seems that it is possible in this code
that the field may not be NULL, but we just saw that on _our_
signature verification system, the primary key is not available.
Shouldn't we be nulling it out, after free()ing possibly leftover
value in the field?

>  						line = next + 1;
>  						next = strchrnul(line, ' ');
>  					}
>  
> -					next = strchrnul(line, '\n');
> -					free(sigc->primary_key_fingerprint);
> -					sigc->primary_key_fingerprint = xmemdupz(line, next - line);
> +					if (j == 0) {
> +						next = strchrnul(line, '\n');
> +						free(sigc->primary_key_fingerprint);
> +						sigc->primary_key_fingerprint =
> +							xmemdupz(line,
> +								 next - line);
> +					}

Avoid such an unnatural line-wrapping that makes the result harder
to read.  A short helper

	static void replace_cstring(const char **field,
				    const char *line, const char *next)
	{
		free(*field);
		if (line && next)
			*field = xmemdupz(line, next - line);
		else
			*field = NULL;
	}

may have quite a lot of uses in this function, not only for this
field.

This is a tangent, but I think "skip 9 fields" loop by itself
deserves to become a small static helper function.

With such a helper, it would become

		if (!j) {
			next = strchrnul(line, '\n');
			replace_cstring(&sigc->primary_key_fingerprint, line, next);
		} else {
			replace_cstring(&sigc->primary_key_fingerprint,	NULL, NULL);
		}

or if you do not like the overlong lines (I don't), perhaps

		field = &sigc->primary_key_fingerprint;
		if (!j)
			replace_cstring(field, line, strchrnul(line, '\n'));
		else
			replace_cstring(field, NULL, NULL);

Note that sigc->key, sigc->signer, sigc->fingerprint fields all
share the same pattern and will benefit from the use of such a
helper function.

Thanks.

[Reference]

*1* Perhaps this one?  https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=dea9d426351e043f872007696e841afb22fe9689;hb=591523ec94b6279b8b39a01501d78cf980de8722#l480


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-18  5:40   ` Junio C Hamano
@ 2019-11-21 23:19     ` Hans Jerry Illikainen
  2019-11-22  2:39       ` Junio C Hamano
  0 siblings, 1 reply; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-21 23:19 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Mon, Nov 18 2019, Junio C Hamano wrote:
> I wonder if it is simpler to define it next to 'next'.  Yes, this
> new variable is used only within this block, but it also gets used
> only in conjunction with that other variable.

Done in v3 (sorry for the messed up versioning going from v0 to v2!).

> A bigger question is, when this happens, what value do we want to
> leave in sigc->primary_key_fingerprint?  As we can see from the
> original code that makes sure the old value in the field will not
> leak by first free()ing, it seems that it is possible in this code
> that the field may not be NULL, but we just saw that on _our_
> signature verification system, the primary key is not available.
> Shouldn't we be nulling it out, after free()ing possibly leftover
> value in the field?

I investigated the code paths to `primary_key_fingerprint` and deduced
that it's only ever touched when GPG_STATUS_FINGERPRINT is encountered
and a primary fingerprint is extracted.  However, v3 will NULL it even
when no primary fingerprint is found.

>> +							xmemdupz(line,
>> +								 next - line);
>> +					}
>
> Avoid such an unnatural line-wrapping that makes the result harder
> to read.

Sorry about that!  I figured that some projects prefer to always trust
in the code formatter; so I just left it be.  Now I know that human
decisions are allowed :)

> A short helper
>
> 	static void replace_cstring(const char **field,
> 				    const char *line, const char *next)
> 	{
> 		free(*field);
> 		if (line && next)
> 			*field = xmemdupz(line, next - line);
> 		else
> 			*field = NULL;
> 	}
>
> may have quite a lot of uses in this function, not only for this
> field.

Implemented.  I wasn't sure whether to do it in a separate commit or
not, but #git-devel suggested that I do; so that's what I did.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v3 0/2] gpg-interface: fix search for primary key fingerprint
  2019-11-16 21:58   ` [PATCH v2 " Hans Jerry Illikainen
  2019-11-16 21:58     ` [PATCH v2 1/1] gpg-interface: limit " Hans Jerry Illikainen
@ 2019-11-21 23:43     ` Hans Jerry Illikainen
  2019-11-21 23:43       ` [PATCH v3 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
                         ` (2 more replies)
  1 sibling, 3 replies; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-21 23:43 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

As part of the process of implementing signature verification for git
clone, I decided to refactor/unify the code for commit and merge
verification to make it reusable during clones.

This lead me to discover that git requires merge signatures to be
trusted (as opposed to TRUST_UNKNOWN or TRUST_NEVER).  This is unlike
the behavior of verify-tag and verify-commit.

So, I figured that I'd make the minimum trust level configurable to make
the behavior of merge/commit/tag consistent.  And while doing so, I
noticed that parse_gpg_output() in gpg-interface.c assumes that the
VALIDSIG status line has a field with a fingerprint for the primary key;
but that is only the case for OpenPGP signatures [1].

The consequence of that assumption is that the subsequent status line is
interpreted as the primary fingerprint for X509 signatures.  I'm not
sure if the order is hardcoded in GnuPG, but in my testing the TRUST_
status line always came after VALIDSIG -- and that breaks the config
option to set a minimum trust level (not part of this patch):

,----
| $ git log -n1 --format="primary key: %GP" signed-x509
| gpgsm: Signature made 2019-11-16 14:13:09 using certificate ID 0xFA23FD65
| gpgsm: Good signature from "/CN=C O Mitter/O=Example/SN=C O/GN=Mitter"
| gpgsm:                 aka "committer@example.com"
| primary key: TRUST_FULLY 0 shell
`----

As per suggestion from Hamano, I also introduced a helper function,
replace_cstring().  I wasn't sure whether to add it in a separate commit
or not, but the kind folks in #git-devel suggested I do.

[1]: https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS

Hans Jerry Illikainen (2):
  gpg-interface: refactor the free-and-xmemdupz pattern
  gpg-interface: limit search for primary key fingerprint

 gpg-interface.c | 45 ++++++++++++++++++++++++++++++++-------------
 t/t4202-log.sh  | 20 ++++++++++++++++++++
 2 files changed, 52 insertions(+), 13 deletions(-)

--
2.24.0.157.gba9f894af8

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v3 1/2] gpg-interface: refactor the free-and-xmemdupz pattern
  2019-11-21 23:43     ` [PATCH v3 0/2] gpg-interface: fix " Hans Jerry Illikainen
@ 2019-11-21 23:43       ` Hans Jerry Illikainen
  2019-11-22  2:45         ` Junio C Hamano
  2019-11-21 23:43       ` [PATCH v3 2/2] gpg-interface: limit search for primary key fingerprint Hans Jerry Illikainen
  2019-11-22 20:23       ` [PATCH v4 0/2] Limit search for primary fingerprint Hans Jerry Illikainen
  2 siblings, 1 reply; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-21 23:43 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

This commit introduces a static replace_cstring() function.  This
function simplifies the continuous pattern of free-and-xmemdupz() for
GPG status line parsing.

The benefit of having it in a single helper function is that it helps
avoid the need for duplicate code that does the same thing.  It also
helps avoid potential memleaks if parsing of new status lines are
introduced in the future.

Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
---
 gpg-interface.c | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index d60115ca40..b4c4443287 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -105,6 +105,17 @@ static struct {
 	{ 0, "VALIDSIG ", GPG_STATUS_FINGERPRINT },
 };
 
+static void replace_cstring(const char **field, const char *line,
+			    const char *next)
+{
+	free(*field);
+
+	if (line && next)
+		*field = xmemdupz(line, next - line);
+	else
+		*field = NULL;
+}
+
 static void parse_gpg_output(struct signature_check *sigc)
 {
 	const char *buf = sigc->gpg_status;
@@ -136,21 +147,18 @@ static void parse_gpg_output(struct signature_check *sigc)
 				/* Do we have key information? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_KEYID) {
 					next = strchrnul(line, ' ');
-					free(sigc->key);
-					sigc->key = xmemdupz(line, next - line);
+					replace_cstring(&sigc->key, line, next);
 					/* Do we have signer information? */
 					if (*next && (sigcheck_gpg_status[i].flags & GPG_STATUS_UID)) {
 						line = next + 1;
 						next = strchrnul(line, '\n');
-						free(sigc->signer);
-						sigc->signer = xmemdupz(line, next - line);
+						replace_cstring(&sigc->signer, line, next);
 					}
 				}
 				/* Do we have fingerprint? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
 					next = strchrnul(line, ' ');
-					free(sigc->fingerprint);
-					sigc->fingerprint = xmemdupz(line, next - line);
+					replace_cstring(&sigc->fingerprint, line, next);
 
 					/* Skip interim fields */
 					for (j = 9; j > 0; j--) {
@@ -162,7 +170,8 @@ static void parse_gpg_output(struct signature_check *sigc)
 
 					next = strchrnul(line, '\n');
 					free(sigc->primary_key_fingerprint);
-					sigc->primary_key_fingerprint = xmemdupz(line, next - line);
+					replace_cstring(&sigc->primary_key_fingerprint,
+							line, next);
 				}
 
 				break;
-- 
2.24.0.157.gba9f894af8


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v3 2/2] gpg-interface: limit search for primary key fingerprint
  2019-11-21 23:43     ` [PATCH v3 0/2] gpg-interface: fix " Hans Jerry Illikainen
  2019-11-21 23:43       ` [PATCH v3 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
@ 2019-11-21 23:43       ` Hans Jerry Illikainen
  2019-11-22  3:34         ` Junio C Hamano
  2019-11-22 20:23       ` [PATCH v4 0/2] Limit search for primary fingerprint Hans Jerry Illikainen
  2 siblings, 1 reply; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-21 23:43 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

The VALIDSIG status line from GnuPG with --status-fd is documented to
have 9 required and 1 optional fields [1].  The final, and optional,
field is used to specify the fingerprint of the primary key that made
the signature in case it was made by a subkey.  However, this field is
only available for OpenPGP signatures; not for CMS/X.509.

The current code assumes that the VALIDSIG status line always has 10
fields.  Furthermore, the current code assumes that each field is
separated by a space (0x20) character.

If the VALIDSIG status line does not have the optional 10th field, the
current code will continue reading onto the next status line -- because
only 0x20 is considered a field separator, not 0xa.  And this is the
case for non-OpenPGP signatures [1].

The consequence is that a subsequent status line may be considered as
the "primary key" for signatures that does not have an actual primary
key.

The solution introduced by this commit is to add 0xa as a bound for the
search for a primary key.  The search for the 10th VALIDSIG field is
aborted as soon as it sees a newline character.  This keeps the parser
from interpreting subsequent lines as the primary key.

[1] https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS#l483

Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
---
 gpg-interface.c | 24 +++++++++++++++++-------
 t/t4202-log.sh  | 20 ++++++++++++++++++++
 2 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index b4c4443287..4269937b83 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -119,7 +119,8 @@ static void replace_cstring(const char **field, const char *line,
 static void parse_gpg_output(struct signature_check *sigc)
 {
 	const char *buf = sigc->gpg_status;
-	const char *line, *next;
+	const char *line, *next, *limit;
+	const char **field;
 	int i, j;
 	int seen_exclusive_status = 0;
 
@@ -160,18 +161,27 @@ static void parse_gpg_output(struct signature_check *sigc)
 					next = strchrnul(line, ' ');
 					replace_cstring(&sigc->fingerprint, line, next);
 
-					/* Skip interim fields */
+					/*
+					 * Skip interim fields.  The search is
+					 * limited to the same line since only
+					 * OpenPGP signatures has a field with
+					 * the primary fingerprint.
+					 */
+					limit = strchrnul(line, '\n');
 					for (j = 9; j > 0; j--) {
-						if (!*next)
+						if (!*next || limit <= next)
 							break;
 						line = next + 1;
 						next = strchrnul(line, ' ');
 					}
 
-					next = strchrnul(line, '\n');
-					free(sigc->primary_key_fingerprint);
-					replace_cstring(&sigc->primary_key_fingerprint,
-							line, next);
+					field = &sigc->primary_key_fingerprint;
+					if (!j) {
+						next = strchrnul(line, '\n');
+						replace_cstring(field, line, next);
+					} else {
+						replace_cstring(field, NULL, NULL);
+					}
 				}
 
 				break;
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index e803ba402e..17ec2401ec 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -1570,6 +1570,14 @@ test_expect_success GPG 'setup signed branch' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPG 'setup signed branch with subkey' '
+        test_when_finished "git reset --hard && git checkout master" &&
+        git checkout -b signed-subkey master &&
+        echo foo >foo &&
+        git add foo &&
+        git commit -SB7227189 -m signed_commit
+'
+
 test_expect_success GPGSM 'setup signed branch x509' '
 	test_when_finished "git reset --hard && git checkout master" &&
 	git checkout -b signed-x509 master &&
@@ -1580,6 +1588,18 @@ test_expect_success GPGSM 'setup signed branch x509' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPGSM 'log x509 fingerprint' '
+        echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
+        git log -n1 --format="%GF | %GP" signed-x509 >actual &&
+        test_cmp expect actual
+'
+
+test_expect_success GPGSM 'log OpenPGP fingerprint' '
+        echo "D4BE22311AD3131E5EDA29A461092E85B7227189" > expect &&
+        git log -n1 --format="%GP" signed-subkey >actual &&
+        test_cmp expect actual
+'
+
 test_expect_success GPG 'log --graph --show-signature' '
 	git log --graph --show-signature -n1 signed >actual &&
 	grep "^| gpg: Signature made" actual &&
-- 
2.24.0.157.gba9f894af8


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-21 23:19     ` Hans Jerry Illikainen
@ 2019-11-22  2:39       ` Junio C Hamano
  2019-11-22  3:44         ` Junio C Hamano
  0 siblings, 1 reply; 21+ messages in thread
From: Junio C Hamano @ 2019-11-22  2:39 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Hans Jerry Illikainen <hji@dyntopia.com> writes:

> On Mon, Nov 18 2019, Junio C Hamano wrote:
> ...
>> A short helper
>>
>> 	static void replace_cstring(const char **field,
>> 				    const char *line, const char *next)
>> 	{
>> 		free(*field);
>> 		if (line && next)
>> 			*field = xmemdupz(line, next - line);
>> 		else
>> 			*field = NULL;
>> 	}
>>
>> may have quite a lot of uses in this function, not only for this
>> field.
>
> Implemented.  I wasn't sure whether to do it in a separate commit or
> not, but #git-devel suggested that I do; so that's what I did.

If such a refactoring helped the readability of _existing_ code that
can also use this helper, then I agree it is the right approach to
make that into a separate prelimimary commit.

Thanks for working on this.


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 1/2] gpg-interface: refactor the free-and-xmemdupz pattern
  2019-11-21 23:43       ` [PATCH v3 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
@ 2019-11-22  2:45         ` Junio C Hamano
  0 siblings, 0 replies; 21+ messages in thread
From: Junio C Hamano @ 2019-11-22  2:45 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Hans Jerry Illikainen <hji@dyntopia.com> writes:

> This commit introduces a static replace_cstring() function.  This
> function simplifies the continuous pattern of free-and-xmemdupz() for
> GPG status line parsing.
>
> The benefit of having it in a single helper function is that it helps
> avoid the need for duplicate code that does the same thing.  It also
> helps avoid potential memleaks if parsing of new status lines are
> introduced in the future.
>
> Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
> ---
>  gpg-interface.c | 23 ++++++++++++++++-------
>  1 file changed, 16 insertions(+), 7 deletions(-)

Looks quite cleanly done and clearly explained.  Thanks.

> diff --git a/gpg-interface.c b/gpg-interface.c
> index d60115ca40..b4c4443287 100644
> --- a/gpg-interface.c
> +++ b/gpg-interface.c
> @@ -105,6 +105,17 @@ static struct {
>  	{ 0, "VALIDSIG ", GPG_STATUS_FINGERPRINT },
>  };
>  
> +static void replace_cstring(const char **field, const char *line,
> +			    const char *next)
> +{
> +	free(*field);
> +
> +	if (line && next)
> +		*field = xmemdupz(line, next - line);
> +	else
> +		*field = NULL;
> +}
> +
>  static void parse_gpg_output(struct signature_check *sigc)
>  {
>  	const char *buf = sigc->gpg_status;
> @@ -136,21 +147,18 @@ static void parse_gpg_output(struct signature_check *sigc)
>  				/* Do we have key information? */
>  				if (sigcheck_gpg_status[i].flags & GPG_STATUS_KEYID) {
>  					next = strchrnul(line, ' ');
> -					free(sigc->key);
> -					sigc->key = xmemdupz(line, next - line);
> +					replace_cstring(&sigc->key, line, next);
>  					/* Do we have signer information? */
>  					if (*next && (sigcheck_gpg_status[i].flags & GPG_STATUS_UID)) {
>  						line = next + 1;
>  						next = strchrnul(line, '\n');
> -						free(sigc->signer);
> -						sigc->signer = xmemdupz(line, next - line);
> +						replace_cstring(&sigc->signer, line, next);
>  					}
>  				}
>  				/* Do we have fingerprint? */
>  				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
>  					next = strchrnul(line, ' ');
> -					free(sigc->fingerprint);
> -					sigc->fingerprint = xmemdupz(line, next - line);
> +					replace_cstring(&sigc->fingerprint, line, next);
>  
>  					/* Skip interim fields */
>  					for (j = 9; j > 0; j--) {
> @@ -162,7 +170,8 @@ static void parse_gpg_output(struct signature_check *sigc)
>  
>  					next = strchrnul(line, '\n');
>  					free(sigc->primary_key_fingerprint);
> -					sigc->primary_key_fingerprint = xmemdupz(line, next - line);
> +					replace_cstring(&sigc->primary_key_fingerprint,
> +							line, next);
>  				}
>  
>  				break;

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v3 2/2] gpg-interface: limit search for primary key fingerprint
  2019-11-21 23:43       ` [PATCH v3 2/2] gpg-interface: limit search for primary key fingerprint Hans Jerry Illikainen
@ 2019-11-22  3:34         ` Junio C Hamano
  0 siblings, 0 replies; 21+ messages in thread
From: Junio C Hamano @ 2019-11-22  3:34 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Hans Jerry Illikainen <hji@dyntopia.com> writes:

> The VALIDSIG status line from GnuPG with --status-fd is documented to
> have 9 required and 1 optional fields [1].  The final, and optional,
> field is used to specify the fingerprint of the primary key that made
> the signature in case it was made by a subkey.  However, this field is
> only available for OpenPGP signatures; not for CMS/X.509.
>
> The current code assumes that the VALIDSIG status line always has 10
> fields.  Furthermore, the current code assumes that each field is
> separated by a space (0x20) character.

This make it sound as if the "assumption" of splitting at SP is a wrong
one, so I went back to the source.  The DETAILS document curiously
forgets to say that these are "usual space delimited arguments" for the
VALIDSIG, unlike it does for a few other entries in the "General status
codes" section.  But with the presense of "usual space delimited" in
another seciton with the lack of explicit delimiter definition in this
section, I would say it is safe to assume that the authors meant these
fields to be separated by SPs. 

In any case, I think this paragraph can safely disappear and would make
the overall clarity of the problem analysis even better.  We already
said that assuming 10th field is wrong in the earlier paragraph, and we
say what happens when the is only 9 fields in the next paragraph.

> If the VALIDSIG status line does not have the optional 10th field, the
> current code will continue reading onto the next status line -- because
> only 0x20 is considered a field separator, not 0xa.  And this is the
> case for non-OpenPGP signatures [1].

I actually think stresing on 0x20 vs 0x0a misleads readers in a wrong
direction.  If the code were written in such a way that both can be used
as a field separator, we would still continue reading into the next
line.  IOW, treating only SP as field delimiter is correct; what is
wrong is we do not limit the search to the current/single line.

	... because the code does not pay attention to the end of the
	current line when it looks for the end of the 10th (and
	optional) field that it incorrectly assumes to exist.

> The consequence is that a subsequent status line may be considered as
> the "primary key" for signatures that does not have an actual primary
> key.

Overall, all of the description in the above paragraphs are nicely
analysed and explained.  I wish all contributors' wrote their proposed
log messages clearly like you.

> The solution introduced by this commit is to add 0xa as a bound for the
> search for a primary key.  The search for the 10th VALIDSIG field is
> aborted as soon as it sees a newline character.  This keeps the parser
> from interpreting subsequent lines as the primary key.

Rather than "... to add 0xa as ... primary key.", I would probably write
it as "... to limit the search for the primary key on the single line."
The end of line being represented (internally as C string) with a byte
whose value is 0x0a or a newline character is an implementation detail.

Also we tend to describe the change we make as if the author is ordering
the codebase to "become like so" in imperative mood, so, perhaps

	Limit the search of these 9 or 10 fields to the single line
	to avoid this problem.  If the 10th field is missing, report
	that there is no primary key fingerprint.

would be sufficient, as the last sentence, i.e. "The consequence
is...", has already hinted what needs to be fixed clearly and quite
directly.


> [1] https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS#l483

I actually wrote the URL with the exact revision in my earlier example
because the URL written without is a moving target.  IOW, "Find the tip
commit of whichever the default branch is, and then look at the path
doc/DETAILS in it and hope that line 483 will stay forever what we want
to refer to" was what I wanted to avoid.  Perhaps in addition to the
above URL (which can go stale), leave a textual reference so that
readers can notice that the link is stale and look for what you meant to
point them at?  Like so:

	[Reference]

        *1* The description for VALIDSIG in the "General status codes"
	section of GnuPG document that is at

	https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS#l483

	says:

        VALIDSIG <args>

        The args are:

        - <fingerprint_in_hex>
        - <sig_creation_date>
        - <sig-timestamp>
        - <expire-timestamp>
        - <sig-version>
        - <reserved>
        - <pubkey-algo>
        - <hash-algo>
        - <sig-class>
        - [ <primary-key-fpr> ]

        This status indicates that the signature is cryptographically
        valid. ... PRIMARY-KEY-FPR is the fingerprint of the primary key
        or identical to the first argument.  This is useful to get back
        to the primary key without running gpg again for this purpose.

        The primary-key-fpr parameter is used for OpenPGP and not
        available for CMS signatures. ...

> @@ -160,18 +161,27 @@ static void parse_gpg_output(struct signature_check *sigc)
>  					next = strchrnul(line, ' ');
>  					replace_cstring(&sigc->fingerprint, line, next);
>  
> -					/* Skip interim fields */
> +					/*
> +					 * Skip interim fields.  The search is
> +					 * limited to the same line since only
> +					 * OpenPGP signatures has a field with
> +					 * the primary fingerprint.
> +					 */
> +					limit = strchrnul(line, '\n');
>  					for (j = 9; j > 0; j--) {
> -						if (!*next)
> +						if (!*next || limit <= next)
>  							break;
>  						line = next + 1;
>  						next = strchrnul(line, ' ');
>  					}

Nice.  We try not to go beyond the end of the current line, and
otherwise break out which leaves j non-zero.

> -					next = strchrnul(line, '\n');
> -					free(sigc->primary_key_fingerprint);
> -					replace_cstring(&sigc->primary_key_fingerprint,
> -							line, next);

And doing the above unconditionally was wrong, but ...

> +					field = &sigc->primary_key_fingerprint;
> +					if (!j) {
> +						next = strchrnul(line, '\n');
> +						replace_cstring(field, line, next);
> +					} else {
> +						replace_cstring(field, NULL, NULL);
> +					}

... we correct it by doing the replacing only when we did find the
10th field.

Nicely done.

By the way, now you have another new variable "field" together with
"limit" both of whose uses are limited to this narrower scope, I no
longer mind seeing declarations of them inside this scope, as opposed to
make them function-wide.  That's a fairly minor point.

> diff --git a/t/t4202-log.sh b/t/t4202-log.sh
> index e803ba402e..17ec2401ec 100755
> --- a/t/t4202-log.sh
> +++ b/t/t4202-log.sh
> @@ -1570,6 +1570,14 @@ test_expect_success GPG 'setup signed branch' '
>  	git commit -S -m signed_commit
>  '
>  
> +test_expect_success GPG 'setup signed branch with subkey' '
> +        test_when_finished "git reset --hard && git checkout master" &&
> +        git checkout -b signed-subkey master &&
> +        echo foo >foo &&
> +        git add foo &&
> +        git commit -SB7227189 -m signed_commit
> +'
> +

The new tests (not only this one) are indented with 8 SPs---will fix
to use HT while queuing (no need to resend only to fix these).

> +test_expect_success GPGSM 'log x509 fingerprint' '
> +        echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
> +        git log -n1 --format="%GF | %GP" signed-x509 >actual &&
> +        test_cmp expect actual
> +'
> +
> +test_expect_success GPGSM 'log OpenPGP fingerprint' '
> +        echo "D4BE22311AD3131E5EDA29A461092E85B7227189" > expect &&
> +        git log -n1 --format="%GP" signed-subkey >actual &&
> +        test_cmp expect actual
> +'

These two tests are really to the point.  Nice.

Thanks.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-22  2:39       ` Junio C Hamano
@ 2019-11-22  3:44         ` Junio C Hamano
  2019-11-22 20:23           ` Hans Jerry Illikainen
  0 siblings, 1 reply; 21+ messages in thread
From: Junio C Hamano @ 2019-11-22  3:44 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Junio C Hamano <gitster@pobox.com> writes:

> Hans Jerry Illikainen <hji@dyntopia.com> writes:
>
>> On Mon, Nov 18 2019, Junio C Hamano wrote:
>> ...
>>> A short helper
>>>
>>> 	static void replace_cstring(const char **field,
>>> 				    const char *line, const char *next)
>>> 	{
>>> 		free(*field);
>>> 		if (line && next)
>>> 			*field = xmemdupz(line, next - line);
>>> 		else
>>> 			*field = NULL;
>>> 	}
>>>
>>> may have quite a lot of uses in this function, not only for this
>>> field.
>>
>> Implemented.  I wasn't sure whether to do it in a separate commit or
>> not, but #git-devel suggested that I do; so that's what I did.
>
> If such a refactoring helped the readability of _existing_ code that
> can also use this helper, then I agree it is the right approach to
> make that into a separate prelimimary commit.

I did not expect the "how about doing it along this line...?"
suggestion written in my MUA without even compilation testing would
work well, and acually I do not think the above would work without
further tweaks on the types.  Wouldn't some of the fields this
helper works on be of type "char *"?

Perhaps something like this squashed in...

 gpg-interface.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index 4269937b83..b481b0811b 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -105,7 +105,7 @@ static struct {
 	{ 0, "VALIDSIG ", GPG_STATUS_FINGERPRINT },
 };
 
-static void replace_cstring(const char **field, const char *line,
+static void replace_cstring(char **field, const char *line,
 			    const char *next)
 {
 	free(*field);
@@ -120,7 +120,6 @@ static void parse_gpg_output(struct signature_check *sigc)
 {
 	const char *buf = sigc->gpg_status;
 	const char *line, *next, *limit;
-	const char **field;
 	int i, j;
 	int seen_exclusive_status = 0;
 
@@ -158,6 +157,8 @@ static void parse_gpg_output(struct signature_check *sigc)
 				}
 				/* Do we have fingerprint? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
+					char **field;
+
 					next = strchrnul(line, ' ');
 					replace_cstring(&sigc->fingerprint, line, next);
 

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-22  3:44         ` Junio C Hamano
@ 2019-11-22 20:23           ` Hans Jerry Illikainen
  2019-11-23  0:18             ` Junio C Hamano
  0 siblings, 1 reply; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-22 20:23 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Fri, Nov 22 2019, Junio C Hamano wrote:
> Wouldn't some of the fields this helper works on be of type "char *"?

Wow, that's embarrassing.  I completely messed that one up after a
looong day.  Gah!  Fixed and re-built with DEVELOPER=1 and re-ran the
test suite for both commits in an attempt to avoid further fuckups.

I also fixed the criticism on 2/2 (even though you mentioned that
there's no need for that) and sent it as v4 because I'm not sure what
the right approach is for changing only 1/2.

For future reference; how does the project prefer fixups for a single
commit on a multi-patch submission?

--
hji

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v4 0/2] Limit search for primary fingerprint
  2019-11-21 23:43     ` [PATCH v3 0/2] gpg-interface: fix " Hans Jerry Illikainen
  2019-11-21 23:43       ` [PATCH v3 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
  2019-11-21 23:43       ` [PATCH v3 2/2] gpg-interface: limit search for primary key fingerprint Hans Jerry Illikainen
@ 2019-11-22 20:23       ` Hans Jerry Illikainen
  2019-11-22 20:23         ` [PATCH v4 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
                           ` (2 more replies)
  2 siblings, 3 replies; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-22 20:23 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

As part of the process of implementing signature verification for git
clone, I decided to refactor/unify the code for commit and merge
verification to make it reusable during clones.

This lead me to discover that git requires merge signatures to be
trusted (as opposed to TRUST_UNKNOWN or TRUST_NEVER).  This is unlike
the behavior of verify-tag and verify-commit.

So, I figured that I'd make the minimum trust level configurable to make
the behavior of merge/commit/tag consistent.  And while doing so, I
noticed that parse_gpg_output() in gpg-interface.c assumes that the
VALIDSIG status line has a field with a fingerprint for the primary key;
but that is only the case for OpenPGP signatures [1].

The consequence of that assumption is that the subsequent status line is
interpreted as the primary fingerprint for X509 signatures.  I'm not
sure if the order is hardcoded in GnuPG, but in my testing the TRUST_
status line always came after VALIDSIG -- and that breaks the config
option to set a minimum trust level (not part of this patch):

,----
| $ git log -n1 --format="primary key: %GP" signed-x509
| gpgsm: Signature made 2019-11-16 14:13:09 using certificate ID 0xFA23FD65
| gpgsm: Good signature from "/CN=C O Mitter/O=Example/SN=C O/GN=Mitter"
| gpgsm:                 aka "committer@example.com"
| primary key: TRUST_FULLY 0 shell
`----

As per suggestion from Hamano, I also introduced a helper function,
replace_cstring().

This patch revision fixes the indentation in the test cases as well as
an erroneous const qualification.

[1]: https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=6ce340e8c04794add995e84308bb3091450bd28f;hb=HEAD#l483


Hans Jerry Illikainen (2):
  gpg-interface: refactor the free-and-xmemdupz pattern
  gpg-interface: limit search for primary key fingerprint

 gpg-interface.c | 44 ++++++++++++++++++++++++++++++++------------
 t/t4202-log.sh  | 20 ++++++++++++++++++++
 2 files changed, 52 insertions(+), 12 deletions(-)

--
2.24.GIT

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v4 1/2] gpg-interface: refactor the free-and-xmemdupz pattern
  2019-11-22 20:23       ` [PATCH v4 0/2] Limit search for primary fingerprint Hans Jerry Illikainen
@ 2019-11-22 20:23         ` Hans Jerry Illikainen
  2019-11-22 20:23         ` [PATCH v4 2/2] gpg-interface: limit search for primary key fingerprint Hans Jerry Illikainen
  2019-11-23  0:22         ` [PATCH v4 0/2] Limit search for primary fingerprint Junio C Hamano
  2 siblings, 0 replies; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-22 20:23 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

This commit introduces a static replace_cstring() function.  This
function simplifies the continuous pattern of free-and-xmemdupz() for
GPG status line parsing.

The benefit of having it in a single helper function is that it helps
avoid the need for duplicate code that does the same thing.  It also
helps avoid potential memleaks if parsing of new status lines are
introduced in the future.

Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
---
 gpg-interface.c | 22 +++++++++++++++-------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index d60115ca40..37162c9a43 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -105,6 +105,16 @@ static struct {
 	{ 0, "VALIDSIG ", GPG_STATUS_FINGERPRINT },
 };
 
+static void replace_cstring(char **field, const char *line, const char *next)
+{
+	free(*field);
+
+	if (line && next)
+		*field = xmemdupz(line, next - line);
+	else
+		*field = NULL;
+}
+
 static void parse_gpg_output(struct signature_check *sigc)
 {
 	const char *buf = sigc->gpg_status;
@@ -136,21 +146,18 @@ static void parse_gpg_output(struct signature_check *sigc)
 				/* Do we have key information? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_KEYID) {
 					next = strchrnul(line, ' ');
-					free(sigc->key);
-					sigc->key = xmemdupz(line, next - line);
+					replace_cstring(&sigc->key, line, next);
 					/* Do we have signer information? */
 					if (*next && (sigcheck_gpg_status[i].flags & GPG_STATUS_UID)) {
 						line = next + 1;
 						next = strchrnul(line, '\n');
-						free(sigc->signer);
-						sigc->signer = xmemdupz(line, next - line);
+						replace_cstring(&sigc->signer, line, next);
 					}
 				}
 				/* Do we have fingerprint? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
 					next = strchrnul(line, ' ');
-					free(sigc->fingerprint);
-					sigc->fingerprint = xmemdupz(line, next - line);
+					replace_cstring(&sigc->fingerprint, line, next);
 
 					/* Skip interim fields */
 					for (j = 9; j > 0; j--) {
@@ -162,7 +169,8 @@ static void parse_gpg_output(struct signature_check *sigc)
 
 					next = strchrnul(line, '\n');
 					free(sigc->primary_key_fingerprint);
-					sigc->primary_key_fingerprint = xmemdupz(line, next - line);
+					replace_cstring(&sigc->primary_key_fingerprint,
+							line, next);
 				}
 
 				break;
-- 
2.24.GIT


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 2/2] gpg-interface: limit search for primary key fingerprint
  2019-11-22 20:23       ` [PATCH v4 0/2] Limit search for primary fingerprint Hans Jerry Illikainen
  2019-11-22 20:23         ` [PATCH v4 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
@ 2019-11-22 20:23         ` Hans Jerry Illikainen
  2019-11-23  0:22         ` [PATCH v4 0/2] Limit search for primary fingerprint Junio C Hamano
  2 siblings, 0 replies; 21+ messages in thread
From: Hans Jerry Illikainen @ 2019-11-22 20:23 UTC (permalink / raw)
  To: git; +Cc: Hans Jerry Illikainen

The VALIDSIG status line from GnuPG with --status-fd is documented to
have 9 required and 1 optional fields [1].  The final, and optional,
field is used to specify the fingerprint of the primary key that made
the signature in case it was made by a subkey.  However, this field is
only available for OpenPGP signatures; not for CMS/X.509.

If the VALIDSIG status line does not have the optional 10th field, the
current code will continue reading onto the next status line.  And this
is the case for non-OpenPGP signatures [1].

The consequence is that a subsequent status line may be considered as
the "primary key" for signatures that does not have an actual primary
key.

The solution introduced by this commit is to limit the search for a
primary key to a single line.  The search for the 10th VALIDSIG field is
aborted as soon as it sees a newline character.  This keeps the parser
from interpreting subsequent lines as the primary key.

[Reference]
[1] GnuPG Details, General status codes
https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=6ce340e8c04794add995e84308bb3091450bd28f;hb=HEAD#l483

The documentation say:

    VALIDSIG <args>

    The args are:

    - <fingerprint_in_hex>
    - <sig_creation_date>
    - <sig-timestamp>
    - <expire-timestamp>
    - <sig-version>
    - <reserved>
    - <pubkey-algo>
    - <hash-algo>
    - <sig-class>
    - [ <primary-key-fpr> ]

    This status indicates that the signature is cryptographically
    valid. [...] PRIMARY-KEY-FPR is the fingerprint of the primary key
    or identical to the first argument.

    The primary-key-fpr parameter is used for OpenPGP and not available
    for CMS signatures.  [...]

Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
---
 gpg-interface.c | 24 ++++++++++++++++++------
 t/t4202-log.sh  | 20 ++++++++++++++++++++
 2 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/gpg-interface.c b/gpg-interface.c
index 37162c9a43..131e7d529e 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -156,21 +156,33 @@ static void parse_gpg_output(struct signature_check *sigc)
 				}
 				/* Do we have fingerprint? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
+					const char *limit;
+					char **field;
+
 					next = strchrnul(line, ' ');
 					replace_cstring(&sigc->fingerprint, line, next);
 
-					/* Skip interim fields */
+					/*
+					 * Skip interim fields.  The search is
+					 * limited to the same line since only
+					 * OpenPGP signatures has a field with
+					 * the primary fingerprint.
+					 */
+					limit = strchrnul(line, '\n');
 					for (j = 9; j > 0; j--) {
-						if (!*next)
+						if (!*next || limit <= next)
 							break;
 						line = next + 1;
 						next = strchrnul(line, ' ');
 					}
 
-					next = strchrnul(line, '\n');
-					free(sigc->primary_key_fingerprint);
-					replace_cstring(&sigc->primary_key_fingerprint,
-							line, next);
+					field = &sigc->primary_key_fingerprint;
+					if (!j) {
+						next = strchrnul(line, '\n');
+						replace_cstring(field, line, next);
+					} else {
+						replace_cstring(field, NULL, NULL);
+					}
 				}
 
 				break;
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index e803ba402e..d18613dad1 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -1570,6 +1570,14 @@ test_expect_success GPG 'setup signed branch' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPG 'setup signed branch with subkey' '
+	test_when_finished "git reset --hard && git checkout master" &&
+	git checkout -b signed-subkey master &&
+	echo foo >foo &&
+	git add foo &&
+	git commit -SB7227189 -m signed_commit
+'
+
 test_expect_success GPGSM 'setup signed branch x509' '
 	test_when_finished "git reset --hard && git checkout master" &&
 	git checkout -b signed-x509 master &&
@@ -1580,6 +1588,18 @@ test_expect_success GPGSM 'setup signed branch x509' '
 	git commit -S -m signed_commit
 '
 
+test_expect_success GPGSM 'log x509 fingerprint' '
+	echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
+	git log -n1 --format="%GF | %GP" signed-x509 >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPGSM 'log OpenPGP fingerprint' '
+	echo "D4BE22311AD3131E5EDA29A461092E85B7227189" > expect &&
+	git log -n1 --format="%GP" signed-subkey >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success GPG 'log --graph --show-signature' '
 	git log --graph --show-signature -n1 signed >actual &&
 	grep "^| gpg: Signature made" actual &&
-- 
2.24.GIT


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/1] gpg-interface: limit search for primary key fingerprint
  2019-11-22 20:23           ` Hans Jerry Illikainen
@ 2019-11-23  0:18             ` Junio C Hamano
  0 siblings, 0 replies; 21+ messages in thread
From: Junio C Hamano @ 2019-11-23  0:18 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Hans Jerry Illikainen <hji@dyntopia.com> writes:

> On Fri, Nov 22 2019, Junio C Hamano wrote:
>> Wouldn't some of the fields this helper works on be of type "char *"?
>
> Wow, that's embarrassing.  I completely messed that one up after a
> looong day.  Gah!  Fixed and re-built with DEVELOPER=1 and re-ran the
> test suite for both commits in an attempt to avoid further fuckups.

The embarrassment is mine ;-)

> I also fixed the criticism on 2/2 (even though you mentioned that
> there's no need for that) and sent it as v4 because I'm not sure what
> the right approach is for changing only 1/2.
>
> For future reference; how does the project prefer fixups for a single
> commit on a multi-patch submission?

Unless the series is insanely loooooooooooooooooooooooooooooooong,
resending the whole thing, optionally with summary of the changes
since the previous iteration for each step after the three-dash
lines (i.e. this allows readers to notice "unchanged since v3"
and skip individual ones marked as such while reviewing v4), would
be a good way to help both reviewers who saw the previous round and
those who have skipped the previous round.

Thanks.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 0/2] Limit search for primary fingerprint
  2019-11-22 20:23       ` [PATCH v4 0/2] Limit search for primary fingerprint Hans Jerry Illikainen
  2019-11-22 20:23         ` [PATCH v4 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
  2019-11-22 20:23         ` [PATCH v4 2/2] gpg-interface: limit search for primary key fingerprint Hans Jerry Illikainen
@ 2019-11-23  0:22         ` Junio C Hamano
  2 siblings, 0 replies; 21+ messages in thread
From: Junio C Hamano @ 2019-11-23  0:22 UTC (permalink / raw)
  To: Hans Jerry Illikainen; +Cc: git

Hans Jerry Illikainen <hji@dyntopia.com> writes:

> ...
> This patch revision fixes the indentation in the test cases as well as
> an erroneous const qualification.

I made minor tweaks to the log message, but otherwise I think this
is ready for 'next'.

Thanks for working on this.

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2019-11-23  0:22 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-16 18:06 [PATCH 0/1] Limit search for primary key fingerprint Hans Jerry Illikainen
2019-11-16 18:06 ` [PATCH 1/1] gpg-interface: limit " Hans Jerry Illikainen
2019-11-18  5:40   ` Junio C Hamano
2019-11-21 23:19     ` Hans Jerry Illikainen
2019-11-22  2:39       ` Junio C Hamano
2019-11-22  3:44         ` Junio C Hamano
2019-11-22 20:23           ` Hans Jerry Illikainen
2019-11-23  0:18             ` Junio C Hamano
2019-11-16 19:49 ` [PATCH 0/1] Limit " Jonathan Nieder
2019-11-16 21:58   ` [PATCH v2 " Hans Jerry Illikainen
2019-11-16 21:58     ` [PATCH v2 1/1] gpg-interface: limit " Hans Jerry Illikainen
2019-11-21 23:43     ` [PATCH v3 0/2] gpg-interface: fix " Hans Jerry Illikainen
2019-11-21 23:43       ` [PATCH v3 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
2019-11-22  2:45         ` Junio C Hamano
2019-11-21 23:43       ` [PATCH v3 2/2] gpg-interface: limit search for primary key fingerprint Hans Jerry Illikainen
2019-11-22  3:34         ` Junio C Hamano
2019-11-22 20:23       ` [PATCH v4 0/2] Limit search for primary fingerprint Hans Jerry Illikainen
2019-11-22 20:23         ` [PATCH v4 1/2] gpg-interface: refactor the free-and-xmemdupz pattern Hans Jerry Illikainen
2019-11-22 20:23         ` [PATCH v4 2/2] gpg-interface: limit search for primary key fingerprint Hans Jerry Illikainen
2019-11-23  0:22         ` [PATCH v4 0/2] Limit search for primary fingerprint Junio C Hamano
2019-11-18  4:45   ` [PATCH 0/1] Limit search for primary key fingerprint Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

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 read-only IMAP folder(s) and NNTP newsgroup(s).