Mailing List Archive

Bug? Vulnerability? gpgme_op_verify_result() can be made to return a list of zero signatures
Hi all,

On 9 June 2020 I disclosed a vulnerability in fwupd. There was a problem with
the way that it used libgpgme to verify the PGP signature of its update
metadata.

I would like to put it forward for wider discussion: is libgpgme is working as
intended, or should this particular behaviour be considered a bug or a
vulnerability in libgpgme?


# How fwupd uses libgpgme

fwupd used gpgme_op_verify() and gpgme_op_verify_result() with a GPG
homedir containing only trusted keys.

If gpgme_op_verify() returned GPG_ERR_NO_ERROR then it looped over the
signatures returned by gpgme_op_verify_result(). If any of those signatures
were "bad" (According to logic implemented by fwupd) then the metadata
signature was deemed "bad". Otherwise, the signature was deemed "good".

This logic is fragile, if not outright incorrect. fwupd is assuming that if it
gets GPG_ERR_NO_ERROR from gpgme_op_verify() then it will get at least one
signature back from gpgme_op_verify_result()

In short, by giving gpgme_op_verify() the following arguments:

* A normal (i.e. NON-DETACHED) signature as 'sig'
* Any data as 'signed_text' (which is a hint to gpgme that 'sig'
should be detached)
* Anything as 'plain'

Then gpgme will attempt to verify the non-detached signature as if it were a
detached signature. I found that this triggers interesting behaviour in
libgpgme, where gpggme_op_verify() will return GPG_ERR_NO_ERROR but
gpgme_op_verify_result() will return a list of zero signatures. This violates
the assumption made by fwupd, which allowed me to bypass its signature
validation.

fwupd fixed this vulnerability on their end by ensuring that libgpgme returned
a non-zero number of signatures. However, I wouldn't be surprised if there were
other software projects making the same assumption, and I think libgpgme could
act more predictably (or indeed "correctly") considering such inputs.

More details on the fwupd vulnerability are available at
https://github.com/justinsteven/advisories/blob/master/2020_fwupd_dangling_s3_bucket_and_CVE-2020-10759_signature_verification_bypass.md

(In particular the section titled "So whose fault was this anyway?")


# Could this be a bug in libgpgme?

During the disclosure process with fwupd I reached out to <security@gnupg.org>.

In short, the developers on duty said that this is expected behaviour from
libgpgme and that fwupd is solely to blame for its insecure use of libgpgme.

The developers on duty made a documentation change cautioning developers of
this behaviour at
<https://dev.gnupg.org/rM88f3202521d422d94bfd79e61bde00707d6f28c9>

I do agree that fwupd was using libgpgme in an unorthodox and very dangerous
way. However, I feel that it is very surprising for gpgme_op_verify() to return
GPG_ERR_NO_ERROR but for gpgme_op_verify_result() to return a list of zero
signatures. This feels like an erroneous condition to me, and with libgpgme
working the way it is, there is the risk of surprising developers and for there
to be verification bypasses in their code.


# Caveat

It is possible that there are many sets of input to gpgme_op_verify() that will
cause it to return zero signatures. I stopped looking for such edge-cases after
I found the one I did.


Justin

_______________________________________________
Gnupg-users mailing list
Gnupg-users@gnupg.org
http://lists.gnupg.org/mailman/listinfo/gnupg-users
Re: Bug? Vulnerability? gpgme_op_verify_result() can be made to return a list of zero signatures [ In reply to ]
Hi!

On Mon, 15 Jun 2020 12:36, Justin Steven said:

> GPG_ERR_NO_ERROR but for gpgme_op_verify_result() to return a list of zero
> signatures. This feels like an erroneous condition to me, and with libgpgme

We already explained that this is a requirement for OpenPGP because
OpenPGP allows to embed a signature in encrypted data (combined method
in contrast to the rarely used MIME containers). Thus when calling the
decrypt function you can't know in advance whether there will be a
signature - not returning an error if there is no signature is proper
behaviour.

More important: Checking the signature is one thing; its result is
basically whether the data is corrupted. The more important step is to
check whether you can trust the key used to generate a signature; this
is basic crypto knowledge which can't be ignored even if you use "GnuPG
Made Easy". GPGME has mechanisms to do this in a not too complicated
way and of course it requires to loop over all signatures.

20 years ago when Debian started to sign packages it was figured that
this is not a trivial task and together we developed gpgv which is a
simple command line tool dedicated to check signatures against a fixed
set of keys. There is no gpgme support for gpgv because calling gpgv is
pretty straightforward.



Shalom-Salam,

Werner


--
Die Gedanken sind frei. Ausnahmen regelt ein Bundesgesetz.
Re: Bug? Vulnerability? gpgme_op_verify_result() can be made to return a list of zero signatures [ In reply to ]
Hi Werner,

Thanks for responding

> this is a requirement for OpenPGP because OpenPGP allows to embed a signature
> in encrypted data (combined method in contrast to the rarely used MIME
> containers). Thus when calling the decrypt function you can't know in
> advance whether there will be a signature - not returning an error if there
> is no signature is proper behaviour.

I'm not referring to any decrypt function. I'm only referring to:

* gpgme_op_verify()
* gpgme_op_verify_result()

I do understand that a PGP message can be both encrypted and signed.

Taking tests/run-verify.c as an example (I only just found it) it seems as
though gpgme_op_verify() doesn't handle encrypted data at all, and the
behaviour I identified is somewhat novel.

```
% ./run-verify <(echo hello | gpg --detach-sign) <(echo hello)
Original file name .: [none]
MIME flag ..........: no
Signature ...: 0
status ....: Success
summary ...: valid green
fingerprint: F7DFBCA15A8D731FB0E7323A8719360278F979DD
created ...: 1592217969
expires ...: 0
validity ..: full
val.reason : Success
pubkey algo: 1 (RSA)
digest algo: 10 (SHA512)
pka address: [none]
pka trust .: n/a
other flags: de-vs
```

Above is expected behaviour

```
% ./run-verify <(echo hello | gpg --sign)
Original file name .: [none]
MIME flag ..........: no
Signature ...: 0
status ....: Success
summary ...: valid green
fingerprint: F7DFBCA15A8D731FB0E7323A8719360278F979DD
created ...: 1592218246
expires ...: 0
validity ..: full
val.reason : Success
pubkey algo: 1 (RSA)
digest algo: 10 (SHA512)
pka address: [none]
pka trust .: n/a
other flags: de-vs
```

Also expected behaviour

```
% ./run-verify <(echo hello | gpg --sign) <(echo hello)
Original file name .: [none]
MIME flag ..........: no
```

Above is the surprising behaviour I'm writing about (No error; no signatures)

```
% ./run-verify <(echo hello | gpg --encrypt -r 'Test Key') <(echo hello)
Original file name .: [none]
MIME flag ..........: no
run-verify: verify failed: General error
```

Above is trying to verify an encrypted message as though it's a detached
signature. General error is expected (There's no signature to verify)

```
% ./run-verify <(echo hello | gpg --encrypt -r 'Test Key')
Original file name .: [none]
MIME flag ..........: no
run-verify: verify failed: General error
```

Above is trying to verify an encrypted message as though it's a regular
signature. General error is expected (There's no signature to verify)

```
% ./run-verify <(echo hello | gpg --sign --encrypt -r 'Test Key') <(echo hello)
Original file name .: [none]
MIME flag ..........: no
run-verify: verify failed: General error
```

Above is trying to verify an encrypted signed message as though it's a detached
signature. General error is probably expected behaviour (I don't know if it
could possibly make sense to ever verify an encrypted message with a detached
signature)

```
% ./run-verify <(echo hello | gpg --sign --encrypt -r 'Test Key')
Original file name .: [none]
MIME flag ..........: no
run-verify: verify failed: General error
```

Above is trying to verify an encrypted signed message as though it's a regular
signature. General error might be unexpected - if gpgme_op_verify() can be used
to check signatures on an encrypted message, is this a functional bug?


(Please do forgive my unfamiliarity with libgpgme. If gpgme_op_verify() can be
used to verify signatures on encrypted messages, do you know where I could find
an example?)


> More important: Checking the signature is one thing; its result is basically
> whether the data is corrupted. The more important step is to check whether
> you can trust the key used to generate a signature; this is basic crypto
> knowledge which can't be ignored even if you use "GnuPG Made Easy". GPGME
> has mechanisms to do this in a not too complicated way and of course it
> requires to loop over all signatures.

I don't disagree at all.

For what it's worth, I'm not trying to shift blame from fwupd to libgpgme. I
believe that fwupd can be (and indeed was) vulnerable; and at the same time,
libgpgme is exhibiting surprising behaviour. In my mind, if libgpgme can be
made to behave more predictably, it's not necessarily taking responsibility for
fwupd's bug. It's an outcome that would serve the greater good.

That is, unless there's a reason why libgpgme's behaviour is actually
functionally required - which I know you're trying to explain, I just can't
think of a concrete example as it relates to gpgme_op_verify(). If you have one
I'd greatly appreciate it.

One last thing, https://www.gnupg.org/documentation/manuals/gpgme/Verify.html
says:

> The function gpgme_op_verify verifies that the signature in the data object
> sig is a valid signature [...] The function returns the error code
> GPG_ERR_NO_ERROR if the operation could be completed successfully, [...]
> GPG_ERR_NO_DATA if sig does not contain any data to verify, and passes
> through any errors that are reported by the crypto engine support routines.

Based on this description, I can't understand how it makes sense for
gpgme_op_verify() to return GPG_ERR_NO_ERROR ("the operation could be completed
successfully") if:

* gpgme_op_verify_result() is going to return zero signatures; and
* gpg would print an error and exit with a non-zero status in a similar case

There is even a GPG_ERR_NO_DATA result that could be returned instead.

Justin

_______________________________________________
Gnupg-users mailing list
Gnupg-users@gnupg.org
http://lists.gnupg.org/mailman/listinfo/gnupg-users