Currently, I am working on a Web-of-Trust implementation for the OpenPGP library PGPainless. This work is being funded by the awesome NLnet foundation through NGI Assure. Check them out! NGI Assure is made possible with financial support from the European Commission’s Next Generation Internet programme.
In this post, I will outline some progress I made towards a full WoT implementation. The current milestone entails integrating certificate stores more closely with the core API.
On most systems, OpenPGP certificates (public keys) are typically stored and managed by GnuPGs internal key store. The downside of this approach is, that applications that want to make use of OpenPGP either need to depend on GnuPG, or are required to manage their very own exclusive certificate store. The latter approach, which e.g. Thunderbird is taking, leads to a situation where there are multiple certificate stores with different contents. Your GnuPG certificate store might contain Bobs certificate, while the Thunderbird store does not. This is confusing for users, as they now have to manage two places with OpenPGP certificates.
There is a proposal for a Shared PGP Certificate Directory nicknamed “pgp.cert.d” which aims to solve this issue by specifying a shared, maildir-like directory for OpenPGP certificates. This directory serves as a single source for all OpenPGP certificates a user might have to deal with. Being well-defined through the standards draft means different applications can access the certificate store without being locked into a single OpenPGP backend.
Since the Web-of-Trust also requires a certificate store of some kind to work on, I thought that pgp.cert.d might be the ideal candidate to implement. During the past months I reworked my existing implementation to allow for different storage backends and defined an abstraction layer for generalized certificate stores (not only pgp.cert.d). This abstraction layer was integrated with PGPainless to allow encryption and verification operations to request certificates from a store. Let me introduce the different components in more detail:
The library pgp-cert-d-java
contains an implementation of the pgp.cert.d specification. It provides an API for applications to store and fetch certificates to and from the pgp.cert.d directory. The task of parsing the certificate material was delegated to the consumer application, so the library is independent from OpenPGP backends.
The library pgp-certificate-store
defines an abstraction layer above pgp-cert-d-java
. It contains interfaces for a general OpenPGP certificate store. Implementations of this interface could for example access GnuPGs certificate store, since the interface does not make assumptions about how the certificates are stored. Inside pgp-cert-d-java
, there is an adapter class that adapts the PGPCertificateDirectory
class to the PGPCertificateStore
interface.
The pgpainless-cert-d
module provides certificate parsing functionality using pgpainless-core
. It further provides a factory class to instantiate PGPainless-backed instances of the PGPCertificateDirectory
interface (both file-based, as well as in-memory directories).
Lastly, the pgpainless-cert-d-cli
application is a command line tool to demonstrate the pgp.cert.d functionality. It can be used to manage the certificate directory by inserting and fetching certificates:
$ pgpainless-cert-d-cli help Store and manage public OpenPGP certificates Usage: certificate-store [-s=DIRECTORY] [COMMAND] Options: -s, --store=DIRECTORY Overwrite the default certificate directory path Commands: help Display the help text for a subcommand export Export all certificates in the store to Standard Output insert Insert or update a certificate import Import certificates into the store from Standard Input get Retrieve certificates from the store setup Setup a new certificate directory list List all certificates in the directory find Lookup primary certificate fingerprints by subkey ids or fingerprints Powered by picocli
Now let’s see how the certificate store can integrate with PGPainless:
Firstly, let’s set up a pgp.cert.d using pgpainless-cert-d-cli:
$ pgpainless-cert-d-cli setup facf859c9dc1106c4a30f56b1c38b70b755017cf
This command initializes the certificate directory in .local/share/pgp.cert.d/
and creates a trust-root key with the displayed fingerprint. This trust-root currently is not of use, but eventually we will use it as the root of trust in the Web-of-Trust.
Just for fun, let’s import our OpenPGP certificates from GnuPG into the pgp.cert.d:
$ gpg --export --armor | pgpainless-cert-d-cli import
The first part of the command exports all public keys from GnuPG, while the second part imports them into the pgp.cert.d directory.
We can now access those certificates like this:
$ pgpainless-cert-d-cli get -a 7F9116FEA90A5983936C7CFAA027DB2F3E1E118A -----BEGIN PGP PUBLIC KEY BLOCK----- Version: PGPainless Comment: 7F91 16FE A90A 5983 936C 7CFA A027 DB2F 3E1E 118A Comment: Paul Schaub <vanitasvitae@fsfe.org> Comment: 2 further identities mQINBFfz1ucBEADXSvUjnOWSzgW5hXki1xUpGv7vacT8XqqGbO9Z32P3eFxa4E9J vveJmx+voxRWpleZ/L6XCYYmCKnagjF0fMxFD1Zxicp5tzbruC1cm/Els0IJVjFV RLke3SegTHxHncA8+BYn2k/VnTKwDXzP0ZLyc7mUbDl8CCtWGGUkXpaa7WyZIA/q [...] -----END PGP PUBLIC KEY BLOCK-----
Would this certificate change over time, e.g. because someone signs it and sends me an updated copy, I could merge the new signatures into the store by simply inserting the updated certificate again:
pgpainless-cert-d-cli insert < update.asc
Now, I said earlier that the benefit of the pgp.cert.d draft was that applications could access the certificate store without the need to rely on a certain backend. Let me demonstrate this by showing how to access my certificate within a Java application without the need to use pgpainless-cert-d-cli
.
First, let’s write a small piece of code which encrypts a message to my certificate:
// Setup the store SubkeyLookupFactory lookupFactory = new DatabaseSubkeyLookupFactory(); PGPCertificateDirectory pgpCertD = PGPainlessCertD.fileBased(lookupFactory); PGPCertificateStoreAdapter store = new PGPCertificateStoreAdapter(pgpCertD); OpenPgpFingerprint myCert = OpenPgpFingerprint.parse("7F9116FEA90A5983936C7CFAA027DB2F3E1E118A"); ByteArrayInputStream plaintext = new ByteArrayInputStream("Hello, World! This message is encrypted using a cert from a store!".getBytes()); ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream(); // Encrypt EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() .onOutputStream(ciphertextOut) .withOptions(ProducerOptions.encrypt( EncryptionOptions.encryptCommunications() .addRecipient(adapter, myCert))); Streams.pipeAll(plaintext, encryptionStream); encryptionStream.close(); System.out.println(ciphertextOut.toString())
In this example, we first set up access to the shared certificate directory. For that we need a method to look up certificates by subkey-ids. In this case this is done through an SQLite database. Next, we instantiate a PGPCertificateDirectory object, which we then wrap in a PGPCertificateStoreAdapter to make it usable within PGPainless.
Next, we only need to know our certificates fingerprint in order to instruct PGPainless to encrypt a message to it. Lastly, we print out the encrypted message.
In the future, once the Web-of-Trust is implemented, it should be possible to pass in the recipients email address instead of the fingerprint. The WoT would then find trustworthy keys with that email address and select those for encryption. Right now though, the user still has to identify trustworthy keys of recipients themselves still.
Similarly, we can use a certificate store when verifying a signed message:
ByteArrayInputStream ciphertextIn = ...; // signed message ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream(); // Verify DecryptionStream verificationStream = PGPainless.decryptAndOrVerify() .onInputStream(ciphertextIn) .withOptions(new ConsumerOptions() .addVerificationCerts(adapter)); Streams.pipeAll(verificationStream, plaintextOut); verificationStream.close(); OpenPgpMetadata result = decryptionStream.getResult(); assertTrue(result.isVerified()); // signature is correct and valid assertTrue(result.containsVerifiedSignatureFrom(myCert));
Here, PGPainless will process the signed message, identify the key that was used for signing and fetch its certificate from the certificate store.
Note, that if you implement verification like that, it is up to you to verify the trustworthiness of the certificate yourself.
In the future, this task will be done by a WoT library in the PGPainless ecosystem automatically though 🙂
The current state of the certificate store integration into PGPainless can be found on the storeIntegration
branch.
One response to “Creating a Web-of-Trust Implementation: Accessing Certificate Stores”
[…] Paul Schaub ☛ Creating a Web-of-Trust Implementation: Accessing Certificate Stores – vanitasvitae’s bl… […]