Creating a Web-of-Trust Implementation: Accessing Certificate Stores


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.

NLnet
NGI Assure

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”

Leave a Reply

Your email address will not be published.