15 releases (breaking)

0.12.0 Jun 14, 2024
0.11.0 Feb 28, 2024
0.10.0 Feb 23, 2024
0.9.0 Dec 6, 2023
0.1.0 Oct 28, 2021

#10 in Authentication

Download history 150/week @ 2024-03-28 192/week @ 2024-04-04 366/week @ 2024-04-11 202/week @ 2024-04-18 122/week @ 2024-04-25 130/week @ 2024-05-02 51/week @ 2024-05-09 165/week @ 2024-05-16 377/week @ 2024-05-23 356/week @ 2024-05-30 456/week @ 2024-06-06 473/week @ 2024-06-13 194/week @ 2024-06-20 95/week @ 2024-06-27 198/week @ 2024-07-04 55/week @ 2024-07-11

558 downloads per month
Used in 4 crates

LGPL-2.0-or-later

1MB
11K SLoC

Sequoia Web of Trust

A Rust library for authenticating bindings between User IDs and certificates using OpenPGP's web of trust. This library is designed around OpenPGP's data structures, but it does not require OpenPGP data. Instead, it is possible to manually describe a web of trust.

This crate also includes a CLI tool, sq-wot, for authenticating bindings and exploring a web of trust.

Introduction

The web of trust is a decentralized trust model popularized by PGP. It is a superset of X.509, which is a hierarchical trust model, and is the most popular trust model on the public internet today. As used on the public internet, however, X.509 relies on a handful of global certification authorities (CAs) who often undermine its security.

The web of trust is more nuanced than X.509. Using the web of trust, require multiple, independent paths to authenticate a binding by only partially trusting CAs. This prevents a single bad actor from compromising their security. And those who have stronger security requirements can use the web of trust in a completely decentralized manner where only the individuals they select---who are not necessarily institutions---act as trusted introducers.

Today, the tooling around the web of trust is primitive at best. Many people interpret this lack of good tooling as a sign that the web of trust is intrinsically difficult to use. We disagree and think that efforts like our OpenPGP CA provide evidence that this is not the case.

See the spec for an in-depth discussion of semantics and implementation.

This library provides functionality to find arbitrary paths in a web-of-trust network, and to validate and lint specific paths. This functionality is also exposed via a command-line tool, sq-wot.

Usage

To use sequoia-wot from your project, you should add the following to your crate's Cargo.toml:

[dependencies]
sequoia-wot = "0.8"
sequoia-cert-store = "0.3"
sequoia-openpgp = { version = "1.0.0", default-features = false }

To compile your crate you would then run:

$ cargo build --release --features sequoia-openpgp/default
$ cargo test --features sequoia-openpgp/default
$ cargo doc --no-deps --features sequoia-openpgp/default

If you do not disable the use of sequoia-openpgp's default features, then sequoia-openpgp will select the default cryptographic backend, and your users won't be able to easily compile your crate with a different cryptographic backend.

sequoia-openpgp currently uses Nettle as its default cryptographic backend. sequoia-openpgp also supports OpenSSL (sequoia-openpgp/crypto-openssl), Botan 3 (sequoia-openpgp/crypto-botan), Botan 2 (sequoia-openpgp/crypto-botan2), Windows CNG (sequoia-openpgp/crypto-cng), and Rust Crypto (sequoia-openpgp/crypto-rust). For more information about building sequoia-openpgp, please refer to sequoia-openpgp's README. This also includes information about the different backends' build requirements.

sq-wot

sq-wot is a CLI to the library. It implements five subcommands: authenticate, lookup, identify, list, and path. All commands authenticate something. In this context, authenticate means to establish the degree to which an identity should be associated with an OpenPGP certificate based on assertions that the user and others have made. Typically, there is a minimal trust threshold, and if the aggregate trust amount is below that, then the command fails.

The first four subcommands authenticate bindings by looking for paths in a web-of-trust network. This is done by computing the maximum flow between trust roots and a binding. Due to the complexity, this can be computationally expensive, but it is quite fast in practice: queries tend to take less than a millisecond. The computation cost is normally dwarfed by the time it takes to parse and validate certificates. The last subcommand authenticates and lints a path. Because the path is known, this is very fast.

In short, authenticate works with a specific binding; lookup finds certificates that have been authenticated for a specific User ID or email address; identify finds User IDs that can be authenticated with a particular certificate; list finds all authenticated bindings; and, path checks a path's authenticity.

authenticate a binding

A binding is a pair consisting of a certificate and a User ID. The certificate is usually denoted by its fingerprint or Key ID. An example of a binding is: 8F17777118A33DDA9BA48E62AACB3243630052D9 and Neal H. Walfield <neal@sequoia-pgp.org>. Another example is CBCD8F030588653EEDD7E2659B7DD433F254904A and Justus Winter <justus@sequoia-pgp.org>.

A binding is authentic if there is a valid path from a trust root, via intermediate introducers, to a target binding. A trust root is a certificate that you rely on to assert the authenticity of bindings. A trust root is also sometimes called a trust anchor. Intermediate introducers are certificates that a trust root or other intermediate introducer delegates its introduction capability to. They are also called certification authorities. Trust roots and intermediate introducers are collectively referred to as trusted introducers.

An example of a path is: 8F17777118A33DDA9BA48E62AACB3243630052D9, CBCD8F030588653EEDD7E2659B7DD433F254904A, Justus Winter <justus@sequoia-pgp.org>. This path includes a root and a target binding, but it does not have any intermediate introducers.

sq-wot authenticate tries to authenticate a binding. It looks like this:

$ sq-wot --gpg authenticate CBCD8F030588653EEDD7E2659B7DD433F254904A 'Justus Winter <justus@sequoia-pgp.org>'
[✓] CBCD8F030588653EEDD7E2659B7DD433F254904A Justus Winter <justus@sequoia-pgp.org>: fully authenticated (133%)
  Path #1 of 2, trust amount 40:
    ◯ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

  Path #2 of 2, trust amount 120:
    ◯ F7173B3C7C685CD9ECC4191B74E445BA0E15C957 ("Neal H. Walfield (Code Signing Key) <neal@pep.foundation>")
    │   certified the following binding on 2022-02-04
    └ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

sq-wot prints out the paths from the trust roots to the specified binding. Because, I also marked the certificate that I'm trying to authenticate as a partially trusted trusted root in gpg, it partially authenticates itself, as the first path shows. The second path is valid, because I directly certified the binding.

The --gpg option tells sq-wot to read my trust roots from gpg and to read gpg's keyring. Trust roots can also be specified explicitly using the --trust-root or -r option. Keyrings can be specified using the --keyring option. For instance, I can confirm that Clint Adams certified dkg's certificate for the User ID <dkg@debian.org>:

$ sq-wot -r F6D3495BB0AE9A02 --keyring /usr/share/keyrings/debian-keyring.gpg authenticate C29F8A0C01F35E34D816AA5CE092EB3A5CA10DBA "<dkg@debian.org>"
[✓] C29F8A0C01F35E34D816AA5CE092EB3A5CA10DBA <dkg@debian.org>: fully authenticated (100%)
   2100A32C46F895AF3A08783AF6D3495BB0AE9A02 ("Clint Adams (GNU) <clint@gnu.org>")
     certified the following binding on 2021-01-01 (expiry: 2023-12-24)
   C29F8A0C01F35E34D816AA5CE092EB3A5CA10DBA "<dkg@debian.org>"

Note that in the above examples, I used the whole User ID, not just the email address. sq-wot also has an --email flag, which causes it to consider all User IDs that contain the specified email address. For instance:

$ sq-wot --gpg authenticate CBCD8F030588653EEDD7E2659B7DD433F254904A --email justus@sequoia-pgp.org
...

Sometimes it is not possible to authenticate a binding. In that case, there are several things that you can try: you can use a public key server to augment your keyring; you can relax the authentication criteria by treating the web of trust as a certification network instead of an authentication network; and, you can query the web of trust to find out what others, who you haven't directly or indirectly designated as trusted introducers, think of a particular binding.

Using a Public Certificate Store

By default, sq-wot only uses the certificates that you explicitly supply to it. It may be that a missing certificate is preventing you from authenticating a binding. If you specify --network then sq-wot will look for missing certificates on a keyserver. (This defaults to hkps://keyserver.ubuntu.com, which is part of the SKS network, and which delivers third-party certifications.) Note: sq-wot does not check for updates, even if a certificate is expired; if the key is present locally, it is used as is. You can keep your gpg keyring up to date by running parcimonie, or by periodically running something like:

$ gpg --export \
  | sq keyring list \
  | awk '{ print $2}' \
  | while read fpr; do \
    sq keyserver -s hkps://keyserver.ubuntu.com get $fpr; \
  done \
  | gpg --import

If --network is provided, sq-wot will also consult a WKD, but it currently only does so for the target certificate.

Certification Networks

Normally, sq-wot works with an authentication network. In an authentication network, there is a difference between Alice asserting that someone, say Mallory, controls a particular key, and Alice asserting that Mallory can be relied upon to make assertions. That is, in an authentication network, verifying someone's identity is fundamentally different from delegating to them, and unconditionally believing what they claim.

In a certification network, all certifications are treated as delegations. Specifically, all certifications are treated as if they have an infinite trust depth, and no regular expressions.

Using a certification network can be dangerous. If you certified Mallory's certificate, then Mallory can cause you to consider any binding to be authenticated. Nevertheless, there are uses for certification networks.

Because trust is earned over time, a certification network provides insight into who potentially useful trusted introducers may be, and their certifications can then be upgraded individually. For instance, Alice may have certified Bob's certificate when she first met him years ago. Now Alice wants to authenticate Carol's certificate. Alice may discover that there is no path to Alice in her authentication network, but she may discover that Bob certified Alice in her certification network. As Alice has known Bob for years, and he has proven reliable, she might decide to make Bob a trusted introducer. That would then allow her to authenticate Carol using her authentication network.

Another use for certification networks is in small closed communities, like the Linux kernel developers, Debian maintainers, or Arch maintainers. Here it is assumed that all members are more or less trusted. Using a certification network, it is possible to see who has vetted whom.

Certification networks are also used by so-called PGP path finding algorithms. This mode matches their behavior.

To tell sq-wot to treat the web of trust as a certification network instead of an authentication network, use the --certification-network option. Note: this can be used for all of sq-wot's subcommands, not just sq-wot authenticate.

Using this option with our first example, we find additional paths from Neal to Justus including:

$ sq-wot --gpg --certification-network authenticate CBCD8F030588653EEDD7E2659B7DD433F254904A 'Justus Winter <justus@sequoia-pgp.org>'
...
  Path #4 of 4, trust amount 120:
    ◯ 8F17777118A33DDA9BA48E62AACB3243630052D9 ("Neal H. Walfield <neal@sequoia-pgp.org>")
    │   certified the following certificate on 2022-10-07
    ├ 653909A2F0E37C106F5FAF546C8857E0D8E8F074 ("Wiktor Kwapisiewicz <wiktor@metacode.biz>")
    │   certified the following certificate on 2018-02-06
    ├ 7420DF86BCE15A458DCE997639278DA8109E6244 ("Guilhem Moulin")
    │   certified the following certificate on 2018-02-10
    ├ 7845120B07CBD8D6ECE5FF2B2A1743EDA91A35B6 ("Darshit Shah <darnir@gmail.com>")
    │   certified the following certificate on 2015-11-09
    ├ D4AB192964F76A7F8F8A9B357BD18320DEADFA11 ("Valodim Skywalker <valodim@mugenguild.com>")
    │   certified the following certificate on 2017-11-19
    ├ CBCD8F030588653EEDD7E2659B7DD433F254904A ("<teythoon@uber.space>")
    │   certified the following binding on 2023-01-24
    └ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

Gossip

Sometimes there are no paths from a trust root to the binding that you are trying to authenticate in the certification network. In that case, it may still be helpful to find out who has certified a particular certificate. That is, it may be helpful to listen to other's gossip. The --gossip option finds arbitrary paths to a particular certificate by treating all certificates as if they were trust roots albeit with zero trust.

Gossip is useful for identifying alternate ways to authenticate a certificate. For instance, imagine Ed wants to authenticate Laura's certificate, but asking her directly is inconvenient. Ed discovers that Micah has certified Laura's certificate, but Ed hasn't yet authenticated Micah's certificate. If Ed is willing to rely on Micah as a trusted introducer, and authenticating Micah's certificate is easier than authenticating Laura's certificate, then Ed has learned about an easier way to authenticate Laura's certificate.

In the following example, we see that a certificate with the self-signed user ID OpenPGP CA <openpgp-ca@sequoia-pgp.org> has certified Justus's certificate:

$ sq-wot --gpg --gossip authenticate CBCD8F030588653EEDD7E2659B7DD433F254904A 'Justus Winter <justus@sequoia-pgp.org>'
...
[ ] CBCD8F030588653EEDD7E2659B7DD433F254904A Justus Winter <justus@sequoia-pgp.org>: not authenticated (0%)
   34F9E4B6A0A70BFEC5AE45198356989DF1977575 ("OpenPGP CA <openpgp-ca@sequoia-pgp.org>")
     certified the following binding on 2022-02-09
   CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

Note: By default, --gossip uses an authentication network. --gossip can be combined with --certification-network for even more unreliable information.

lookup by User ID or email address

The lookup subcommand finds certificates that are authenticated for the specified User ID. This is useful when you want to find someone's certificate. As before, we can use the --email option:

$ sq-wot --gpg lookup --email justus@sequoia-pgp.org
[✓] CBCD8F030588653EEDD7E2659B7DD433F254904A Justus Winter <justus@sequoia-pgp.org>: fully authenticated (133%)
  Path #1 of 2, trust amount 40:
    ◯ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

  Path #2 of 2, trust amount 120:
    ◯ F7173B3C7C685CD9ECC4191B74E445BA0E15C957 ("Neal H. Walfield (Code Signing Key) <neal@pep.foundation>")
    │   certified the following binding on 2022-02-04
    └ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

The lookup subcommand is also a useful tool to evaluate the degree to which the same User ID is authenticated for different certificates. This can help distinguish if a certificate is a forgery, or a legitimate replacement.

This is visualized in the following image, which was created using sq-wot's DOT output format option, and converted to SVG using Graphiz's DOT compiler:

$ sq-wot \
    -r 2AC0A42EFB0B5CBC7A0402ED4DC95B6D7BE9892E \
    -r D8AFDDA07A5B6EDFA7D8CCDAD6D055F927843F1C \
    -r 75BD80E4D834509F6E740257B1B73B02CC52A02A \
    -r 91FFE0700E80619CEB73235CA88E23E377514E00 \
    -r 0E8B644079F599DFC1DDC3973348882F6AC6A4C2 \
    -r 69E6471E3AE065297529832E6BA0F5A2037F4F41 \
    -k archlinux.pgp \
    -f dot \
    -a 500 \
    lookup \
    --email dvzrv@archlinux.org | dot -Tsvg

A graph showing various trust roots of the Arch Linux distribution with certifications towards two packager keys

identify by certificate

The identify subcommand finds all bindings that can be authenticated for a given certificate. This is useful when you want to figure out who a certificate belongs to:

$ sq-wot --gpg identify CBCD8F030588653EEDD7E2659B7DD433F254904A
[✓] CBCD8F030588653EEDD7E2659B7DD433F254904A Justus Winter <justus@pep.foundation>: fully authenticated (133%)
  Path #1 of 2, trust amount 40:
    ◯ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@pep.foundation>"

  Path #2 of 2, trust amount 120:
    ◯ F7173B3C7C685CD9ECC4191B74E445BA0E15C957 ("Neal H. Walfield (Code Signing Key) <neal@pep.foundation>")
    │   certified the following binding on 2022-02-04
    └ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@pep.foundation>"

[✓] CBCD8F030588653EEDD7E2659B7DD433F254904A Justus Winter <justus@sequoia-pgp.org>: fully authenticated (133%)
  Path #1 of 2, trust amount 40:
    ◯ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

  Path #2 of 2, trust amount 120:
    ◯ F7173B3C7C685CD9ECC4191B74E445BA0E15C957 ("Neal H. Walfield (Code Signing Key) <neal@pep.foundation>")
    │   certified the following binding on 2022-02-04
    └ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"
...

The identify subcommand also shows that different User IDs on the same certificate may not all be authenticated to the same degree.

This is illustrated in the following graphic, which was created using the DOT output format:

$ sq-wot \
    -r 2AC0A42EFB0B5CBC7A0402ED4DC95B6D7BE9892E \
    -r D8AFDDA07A5B6EDFA7D8CCDAD6D055F927843F1C \
    -r 75BD80E4D834509F6E740257B1B73B02CC52A02A \
    -r 91FFE0700E80619CEB73235CA88E23E377514E00 \
    -r 0E8B644079F599DFC1DDC3973348882F6AC6A4C2 \
    -r 69E6471E3AE065297529832E6BA0F5A2037F4F41 \
    -k archlinux.pgp \
    -f dot \
    -a 500 \
    identify \
    B5971F2C5C10A9A08C60030F786C63F330D7CB92 | dot -Tsvg

A graph showing various trust roots of the Arch Linux distribution with certifications towards a packager key

list all authenticated bindings

The list subcommand finds all bindings that can be authenticated for all certificates. This is useful for seeing everyone you can authenticate, and why:

$ sq-wot --gpg list
[✓] 8F17777118A33DDA9BA48E62AACB3243630052D9 Neal H. Walfield <neal@pep.foundation>: fully authenticated (100%)
   8F17777118A33DDA9BA48E62AACB3243630052D9 "Neal H. Walfield <neal@pep.foundation>"

[✓] 8F17777118A33DDA9BA48E62AACB3243630052D9 Neal H. Walfield <neal@sequoia-pgp.org>: fully authenticated (100%)
   8F17777118A33DDA9BA48E62AACB3243630052D9 "Neal H. Walfield <neal@sequoia-pgp.org>"
...
[✓] CBCD8F030588653EEDD7E2659B7DD433F254904A Justus Winter <justus@sequoia-pgp.org>: fully authenticated (133%)
  Path #1 of 2, trust amount 40:
    ◯ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"

  Path #2 of 2, trust amount 120:
    ◯ F7173B3C7C685CD9ECC4191B74E445BA0E15C957 ("Neal H. Walfield (Code Signing Key) <neal@pep.foundation>")
    │   certified the following binding on 2022-02-04
    └ CBCD8F030588653EEDD7E2659B7DD433F254904A "Justus Winter <justus@sequoia-pgp.org>"
...

This can also be helpful for examining connections within a community. The following graph was generated using sq-wot's DOT output, and shows bindings that can be authenticated using the Linux kernel's keyring from three community members.

$ sq-wot \
  -f dot \
  -r ABAF11C65A2970B130ABE3C479BE3E4300411886 \
  -r 647F28654894E3BD457199BE38DBBDC86092693E \
  -r CA30110FD5285DE49BB238CD17212997986C5765 \
  -k kernel.pgp \
  list | dot -Tsvg

A graph showing three Linux kernel developers and the keys they are signing

Verify a path

The path subcommand verifies and lints a path. This is particularly useful when you think that a path should be valid, but sq-wot authenticate silently disagrees.

The path is passed to sq-wot path as a list of fingerprints starting with the root's fingerprint or Key ID and ending with the target certificate's fingerprint or Key ID, and a User ID. If the path is valid, it returns success. If it is not valid, it lints the path.

In the following transcript, we first see that sq-wot authenticate unhelpfully tells us that there are no paths from hpa to Greg Kroah-Hartman's certificate. But when we use sq-wot path, we find out that hpa did certify Greg Kroah-Hartman's certificate, but the certification uses SHA-1, which is no longer trusted:

$ sq-wot --keyring kernel.pgp -r BDA06085493BACE4 authenticate 38DBBDC86092693E "Greg Kroah-Hartman (Linux kernel stable release signing key) <greg@kroah.com>"
No paths found.
$ sq-wot --keyring kernel.pgp path BDA06085493BACE4 38DBBDC86092693E "Greg Kroah-Hartman (Linux kernel stable release signing key) <greg@kroah.com>"
[ ] 647F28654894E3BD457199BE38DBBDC86092693E Greg Kroah-Hartman (Linux kernel stable release signing key) <greg@kroah.com>: not authenticated (0%)
   7EAAC9693E7D220546BE576CBDA06085493BACE4 ("H. Peter Anvin (hpa) <hpa@zytor.com>")
     No adequate certification found.
     BDA06085493BACE4 did not certify <38DBBDC86092693E, "Greg Kroah-Hartman (Linux kernel stable release signing key) <greg@kroah.com>">
     No active certifications by BDA06085493BACE4 for <38DBBDC86092693E, "Greg Kroah-Hartman (Linux kernel stable release signing key) <greg@kroah.com>"> had a trust amount of at least 120
     Certification (B122 by BDA06085493BACE4 on 38DBBDC86092693E at 2011-09-23 18:42.09) is adequate, but it is not valid
       B122 by BDA06085493BACE4 on 38DBBDC86092693E at 2011-09-23 18:42.09: policy violation
       Policy rejected non-revocation signature (GenericCertification) requiring collision resistance
       SHA1 is not considered secure since 2013-02-01T00:00:00Z
   647F28654894E3BD457199BE38DBBDC86092693E "Greg Kroah-Hartman (Linux kernel stable release signing key) <greg@kroah.com>"

License

sequoia-wot is distributed under the terms of LGPL 2.0 or later.

See LICENSE.txt and CONTRIBUTING.md for details.

Dependencies

~38–54MB
~867K SLoC