We are very proud to announce the release of PGPainless-WOT, an implementation of the OpenPGP Web of Trust specification using PGPainless.
The release is available on the Maven Central repository.
The work on this project begun a bit over a year ago as an NLnet project which received funding through the European Commission’s NGI Assure program. Unfortunately, somewhere along the way I lost motivation to work on the project, as I failed to see any concrete users. Other projects seemed more exciting at the time.
Fast forward to end of May when Wiktor reached out and connected me with Heiko, who was interested in the project. We two decided to work together on the project and I quickly rebased my – at this point ancient and outdated – feature branch onto the latest PGPainless release. At the end of June, we started the joint work and roughly a month later today, we can release a first version ๐
Big thanks to Heiko for his valuable contributions and the great boost in motivation working together gave me ๐
Also big thanks to NLnet for sponsoring this project in such a flexible way.
Lastly, thanks to Wiktor for his talent to connect people ๐
The Implementation
We decided to write the implementation in Kotlin. I had attempted to learn Kotlin multiple times before, but had quickly given up each time without an actual project to work on. This time I stayed persistent and now I’m a convinced Kotlin fan ๐ Rewriting the existing codebase was a breeze and the line count drastically reduced while the amount of syntactic sugar which was suddenly available blow me away! Now I’m considering to steadily port PGPainless to Kotlin. But back to the Web-of-Trust.
Our implementation is split into 4 modules:
pgpainless-wot
parses OpenPGP certificates into a generalized form and builds a flow network by verifying third-party signatures. It also provides a plugin forpgpainless-core
.wot-dijkstra
implements a query algorithm that finds paths on a network. This module has no OpenPGP dependencies whatsoever, so it could also be used for other protocols with similar requirements.pgpainless-wot-cli
provides a CLI frontend forpgpainless-wot
wot-test-suite
contains test vectors from Sequoia PGP’s WoT implementation
The code in pgpainless-wot
can either be used standalone via a neat little API, or it can be used as a plugin for pgpainless-core
to enhance the encryption / verification API:
/* Standalone */ Network network = PGPNetworkParser(store).buildNetwork(); WebOfTrustAPI api = new WebOfTrustAPI(network, trustRoots, false, false, 120, refTime); // Authenticate a binding assertTrue( api.authenticate(fingerprint, userId, isEmail).isAcceptable()); // Identify users of a certificate via the fingerprint assertEquals( "Alice <alice@example.org>", api.identify(fingerprint).get(0).getUserId()); // Lookup certificates of users via userId LookupAPI.Result result = api.lookup( "Alice <alice@example.org>", isEmail); // Identify all authentic bindings (all trustworthy certificates) ListAPI.Result result = api.list(); /* Or enhancing the PGPainless API */ CertificateAuthorityImpl wot = CertificateAuthorityImpl .webOfTrustFromCertificateStore(store, trustRoots, refTime) // Encryption EncryptionStream encStream = PGPainless.encryptAndOrSign() [...] // Add only recipients we can authenticate .addAuthenticatableRecipients(userId, isEmail, wot) [...] // Verification DecryptionStream decStream = [...] [...] // finish decryption MessageMetadata metadata = decStream.getMetadata(); assertTrue(metadata.isAuthenticatablySignedBy(userId, isEmail, wot));
The CLI application pgpainless-wot-cli
mimics Sequoia PGP’s neat sq-wot tool, both in argument signature and output format. This has been done in an attempt to enable testing of both applications using the same test suite.
pgpainless-wot-cli
can read GnuPGs keyring, can fetch certificates from the Shared OpenPGP Certificate Directory (using pgpainless-cert-d of course :P) and ingest arbitrary .pgp keyring files.
$ ./pgpainless-wot-cli help Usage: pgpainless-wot [--certification-network] [--gossip] [--gpg-ownertrust] [--time=TIMESTAMP] [--known-notation=NOTATION NAME]... [-r=FINGERPRINT]... [-a=AMOUNT | --partial | --full | --double] (-k=FILE [-k=FILE]... | --cert-d[=PATH] | --gpg) [COMMAND] -a, --trust-amount=AMOUNT The required amount of trust. --cert-d[=PATH] Specify a pgp-cert-d base directory. Leave empty to fallback to the default pgp-cert-d location. --certification-network Treat the web of trust as a certification network instead of an authentication network. --double Equivalent to -a 240. --full Equivalent to -a 120. --gossip Find arbitrary paths by treating all certificates as trust-roots with zero trust. --gpg Read trust roots and keyring from GnuPG. --gpg-ownertrust Read trust-roots from GnuPGs ownertrust. -k, --keyring=FILE Specify a keyring file. --known-notation=NOTATION NAME Add a notation to the list of known notations. --partial Equivalent to -a 40. -r, --trust-root=FINGERPRINT One or more certificates to use as trust-roots. --time=TIMESTAMP Reference time. Commands: authenticate Authenticate the binding between a certificate and user ID. identify Identify a certificate via its fingerprint by determining the authenticity of its user IDs. list Find all bindings that can be authenticated for all certificates. lookup Lookup authentic certificates by finding bindings for a given user ID. path Verify and lint a path. help Displays help information about the specified command
The README file of the pgpainless-wot-cli
module contains instructions on how to build the executable.
Future Improvements
The current implementation still has potential for improvements and optimizations. For one, the Network object containing the result of many costly signature verifications is currently ephemeral and cannot be cached. In the future it would be desirable to change the network parsing code to be agnostic of reference time, including any verifiable signatures as edges of the network, even if those signatures are not yet – or no longer valid. This would allow us to implement some caching logic that could write out the network to disk, ready for future web of trust operations.
That way, the network would only need to be re-created whenever the underlying certificate store is updated with new or changed certificates (which could also be optimized to only update relevant parts of the network). The query algorithm would need to filter out any inactive edges with each query, depending on the queries reference time. This would be far more efficient than re-creating the network with each application start.
But why the Web of Trust?
End-to-end encryption suffers from one major challenge: When sending a message to another user, how do you know that you are using the correct key? How can you prevent an active attacker from handing you fake recipient keys, impersonating your peer? Such a scenario is called Machine-in-the-Middle (MitM) attack.
On the web, the most common countermeasure against MitM attacks are certificate authorities, which certify the TLS certificates of website owners, requiring them to first prove their identity to some extent. Let’s Encrypt for example first verifies, that you control the machine that serves a domain before issuing a certificate for it. Browsers trust Let’s Encrypt, so users can now authenticate your website by validating the certificate chain from the Let’s Encrypt CA key down to your website’s certificate.
The Web-of-Trust follows a similar model, with the difference, that you are your own trust-root and decide, which CA’s you want to trust (which in some sense makes you your own “meta-CA”). The Web-of-Trust is therefore far more decentralized than the fixed set of TLS trust-roots baked into web browsers. You can use your own key to issue trust signatures on keys of contacts that you know are authentic. For example, you might have met Bob in person and he handed you a business card containing his key’s fingerprint. Or you helped a friend set up their encrypted communications and in the process you two exchanged fingerprints manually.
In all these cases, in order to initiate a secure communication channel, you needed to exchange the fingerprint via an out-of-band channel. The real magic only happens, once you take into consideration that your close contacts could also do the same for their close contacts, which makes them CAs too. This way, you could authenticate Charlie via your friend Bob, of whom you know that he is trustworthy, because – come on, it’s Bob! Everybody loves Bob!
The Web-of-Trust becomes really useful if you work with people that share the same goal. Your workplace might be one of them, your favorite Linux distribution’s maintainer team, or that non-Profit organization/activist collective that is fighting for a better tomorrow. At work for example, your employer’s IT department might use a local CA (such as an instance of the OpenPGP CA) to help employees to communicate safely. You trust your workplace’s CA, which then introduces you safely to your colleagues’ authentic key material. It even works across business’ boundaries, e.g. if your workplace has a cooperation with ACME and you need to establish a safe communication channel to an ACME employee. In this scenario, your company’s CA might delegate to the ACME CA, allowing you to authenticate ACME employees.
As you can see, the Web-of-Trust becomes more useful the more people are using it. Providing accessible tooling is therefore essential to improve the overall ecosystem. In the future, I hope that OpenPGP clients such as MUAs (e.g. Thunderbird) will embrace the Web-of-Trust.