Page MenuHome GnuPG

WoT: adding a marginal trustsig reduces the validity of a downstream certificate
Closed, ResolvedPublic

Description

Heiko Schaefer suggested to me that there was some strange things happening in GnuPG's "Web of Trust" calculations.

Running the above script we see:

$ ./wot-strangeness
: Alice trust-signs   Bob with     full (depth 2)
:   Bob trust-signs Carol with     full (depth 1)
:  Bill trust-signs Carol with marginal (depth 1)
: Carol  certifies  Dave

Alice's view of Dave:
pub   ed25519 2025-04-16 [SC] [expires: 2028-04-15]
      A4D12801C5F7AA0F815B04B1207C982395DCA708
uid           [  full  ] Dave

: Alice trust-signs  Bill with marginal (depth 2)

Alice's revised view of Dave:
pub   ed25519 2025-04-16 [SC] [expires: 2028-04-15]
      A4D12801C5F7AA0F815B04B1207C982395DCA708
uid           [marginal] Dave

$ 

Specifically: Alice tsigning Bill *reduces* her confidence in the validity of Dave's certificate. How is it possible that adding a trust-signature would reduce validity?

Before that final tsign, the graph looks like this:

      ⓕ2    ⓕ1
Alice —→ Bob —→ Carol → Dave [full]
              🡕ⓜ1
         Bill

and Dave's certificate is considered fully valid.

But after Alice adds the marginal tsig to Bill, Dave's certificate is only marginally valid:

      ⓕ2    ⓕ1
Alice —→ Bob —→ Carol → Dave [marginal]
  ⓜ2 🡖        🡕ⓜ1
         Bill

What's going on here? Is there something i'm misunderstanding about how GnuPG evaluates marginal trust signatures?

Details

Version
2.4.7

Event Timeline

dkg updated the task description. (Show Details)
werner added a subscriber: werner.

I also spend some time with this and the problem is described by this comment in trustdb.c:

	      /* Are we part of a trust sig chain?  We always favor
                 the latest trust sig, rather than the greater or
                 lesser trust sig or value.  I could make a decent
                 argument for any of these cases, but this seems to be
                 what PGP does, and I'd like to be compatible. -dms */

To test this I removed the signatures from Carol and Bill and then signed Carol's key first by Bill and then by Bob. Dave gets a validity of full. Now, when Alice signs Bill's keys the validity of Dave is still full.

FWIW, using gpg --check-trustdb --debug trust gives some insights into the computations. Here is an example with one additional key and in the original setting:

gpg: 6 keys processed (6 validity counts cleared)
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: DBG: trust sig on bob, sig depth is 2, kr depth is 0
gpg: DBG: replacing trust value 0 with 120 and depth 0 with 2
gpg: DBG: trust sig on eve@example.org, sig depth is 1, kr depth is 0
gpg: DBG: replacing trust value 0 with 120 and depth 0 with 1
gpg: DBG: trust sig on bill, sig depth is 2, kr depth is 0
gpg: DBG: replacing trust value 0 with 60 and depth 0 with 2
gpg: depth: 0  valid:   1  signed:   3  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: DBG: key of bill: update min_ownertrust from 0 to 4
gpg: DBG: key of bill: overriding ownertrust 'undefined' with 'marginal'
gpg: DBG: key of eve: update min_ownertrust from 0 to 5
gpg: DBG: key of eve: overriding ownertrust 'undefined' with 'full'
gpg: DBG: key of bob: update min_ownertrust from 0 to 5
gpg: DBG: key of bob: overriding ownertrust 'undefined' with 'full'
gpg: DBG: trust sig on carol, sig depth is 1, kr depth is 1
gpg: DBG: replacing trust value 0 with 120 and depth 0 with 1
gpg: DBG: trust sig on carol, sig depth is 1, kr depth is 1
gpg: DBG: replacing trust value 120 with 60 and depth 0 with 1
gpg: depth: 1  valid:   3  signed:   1  trust: 0-, 0q, 0n, 1m, 2f, 0u
gpg: DBG: key of carol: update min_ownertrust from 0 to 4
gpg: DBG: key of carol: overriding ownertrust 'undefined' with 'marginal'
gpg: depth: 2  valid:   1  signed:   1  trust: 0-, 0q, 0n, 1m, 0f, 0u
gpg: next trustdb check due at 2028-04-28

I manually changed dumped keyids to "of Bob" etc. Should be done by the code but we have only the keyid as items and the code does not do another lookup.

Interesting analysis, thanks for the sleuthing! I'm not sure i understand why "the latest" should be preferred. For example, in the graph made in this example, which part of the graph is the "latest"? Since the path from Alice to Carol is two hops long at least, it's conceivable that one path (A→Bob→C) has both "the latest" tsig *and* "the earliest" tsig, if the other path (A→Bill→C) happens to have been made between the other two tsigs.

And what does it mean if the certifications in question are all made at the same time (as happens when the sample script above executes between 1s wall-clock boundaries)? Somehow the path through Bill is getting prioritized.

Also, my understanding is that three marginally-trusted certifications should be equivalent to a fully-trusted certification in the standard model, right? So there's some sense that corroborative paths are expected to be additive. But if they're additive, then why would the addition of a marginally-trusted certification reduce the fully-trusted one?

I'm surprised to see this labelled "not a bug", as it definitely smells like a bug to me. Are certifications additive or not?

A bit more experimentation shows the same behavior, even if Alice's tsig of Bill is full, not marginal, and even if all signatures are made in the same second, which is the finest resolution that OpenPGP objects can report.

That is:

      ⓕ2    ⓕ1
Alice —→ Bob —→ Carol → Dave [marginal]
  ⓕ2 🡖        🡕ⓜ1
         Bill

I also tried shuffling the placement of the certificates in Alice's keyring, even when all certifications are made in the same second, to see whether the order in which the certificates show up makes a difference, and it doesn't seem to change anything.

werner claimed this task.

> I'm not sure i understand why "the latest" should be preferred.

Let me repeat Davids rationale for only looking at the latest signature:

but this seems to be what PGP does, and I'd like to be compatible. -dms

And thus we won't change a >25 year old behaviour.

I see two interesting angles from which to think about this Web of Trust calculation:

  1. Is there a way for a human observer to make sense of what GnuPG does?
  2. Is it compatible with the non-free PGP software?

Regarding 1., I don't think anyone would expect that the calculated validity of a User ID binding will ever get *worse* by adding more certifying signatures.

And regarding 2, when handing the analogous set of public keys and certifying signatures to PGP 6.5.8, it calculates the validities that I would expect:

# pgp -kc
Pretty Good Privacy(tm) Version 6.5.8
(c) 1999 Network Associates Inc.
Uses the RSAREF(tm) Toolkit, which is copyright RSA Data Security, Inc.
Export of this software may be restricted by the U.S. government.


Key ring: '/root/.pgp/pubring.pkr'
Type bits      keyID      Date       User ID
RSA  2048      0xBDA2B4C6 2025/05/04 *** DEFAULT SIGNING KEY ***
                                     <alice@example.org>
sig!           0xBDA2B4C6             <alice@example.org>
RSA  2048      0xEF44C2E7 2025/05/04 <bill@example.org>
sig!           0xEF44C2E7             <bill@example.org>
sig!           0xBDA2B4C6             <alice@example.org>
RSA  2048      0x2CF6FCE8 2025/05/04 <bob@example.org>
sig!           0x2CF6FCE8             <bob@example.org>
sig!           0xBDA2B4C6             <alice@example.org>
RSA  2048      0x46C547D1 2025/05/04 <carol@example.org>
sig!           0x46C547D1             <carol@example.org>
sig!           0x2CF6FCE8             <bob@example.org>
sig!           0xEF44C2E7             <bill@example.org>
RSA  2048      0x659CDD3C 2025/05/04 <dave@example.org>
sig!           0x659CDD3C             <dave@example.org>
sig!           0x46C547D1             <carol@example.org>
5 matching keys found.

  KeyID      Trust     Validity  User ID
  0x659CDD3C untrusted complete  <dave@example.org>
             untrusted            <dave@example.org>
             untrusted            <carol@example.org>
  0x2CF6FCE8 untrusted complete  <bob@example.org>
             untrusted            <bob@example.org>
c            ultimate             <alice@example.org>
  0x46C547D1 untrusted complete  <carol@example.org>
             untrusted            <carol@example.org>
             untrusted            <bob@example.org>
             untrusted            <bill@example.org>
* 0xBDA2B4C6 ultimate  complete  <alice@example.org>
c            ultimate             <alice@example.org>
  0xEF44C2E7 untrusted complete  <bill@example.org>
             untrusted            <bill@example.org>
c            ultimate             <alice@example.org>

It would seem that the claim that GnuPG's behavior serves compatibility with PGP does not hold water.

Heiko, I told you already in T7106 that it is not a good idea to re-open a ticket. If you really want to discuss stuff, take that to a mailing list.

I am surprised that you don't want to use the issue tracker for issues.
GnuPG's trust calculations are quite clearly broken, by any metric. There's nothing to discuss here.

But as I already wrote in T7106: it's your project.
If you want to keep this broken, I clearly can't stop you.

A bug tracker shall never be used for discussion because the audience is not as expected. Only very few people follow a certain bug but several hundreds are following discussion on gnupg-devel@. That is basic hacker knowledge.

For the records:

GnuPG's trust calculations are quite clearly broken, by any metric. There's nothing to discuss here.

I described how the key validation is done and why it has been done so. Yes, there is nothing to discuss because the rationale for the behaviour has been given 23 years ago:

We always favor the latest trust sig, rather than the greater or lesser trust sig or value.

To avoid further noise on this ticket, i've done as requested and posted to gnupg-devel : https://lists.gnupg.org/pipermail/gnupg-devel/2025-May/035875.html