Creating a Web-of-Trust Implementation: Certify Keys with PGPainless


Currently I am working on a Web-of-Trust implementation for the OpenPGP library PGPainless. This work will be 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

Technically, the WoT consists of a graph where the nodes are OpenPGP keys (certificates) with User-IDs and the edges are signatures. I recommend watching this talk by Justus Winter (22:51) to get an overview of what the benefits of the WoT are. In order to be able to create a WoT, users need to be able to sign other users certificates to create those edges.

Therefore, support for signing other certificates and User-IDs was added to PGPainless as a milestone of the project. Since release 1.3.2, users have access to a straight-forward API to create signatures over other users certificates. Let’s explore the new API together!

There are two main categories of signatures which are important for the WoT:

  • Certifications are signatures over User-IDs on certificates. A certification is a statement “I believe that the person holding this certificate goes by the name ‘Alice alice@pgpainless.org‘”.
  • Delegations are signatures over certificates which can be used to delegate trust decisions to the holder of the signed certificate.

This is an example for how a user can certify a User-ID:

PGPSecretKeyRing aliceKey = ...;
PGPPublicKeyRing bobsCertificate = ...;

CertificationResult result = PGPainless.certify()
        .userIdOnCertificate("Bob Babbage <bob@pgpainless.org>",
                bobsCertificate)
        .withKey(aliceKey, protector)
        .build();

PGPSignature certification = result.getCertification();
// or...
PGPPublicKeyRing certifiedCertificate = result.getCertifiedCertificate();

It is possible to choose between different certification types, depending on the “quality” of the certification. By default, the certification is created as GENERIC_CERTIFICATION, but other levels can be chosen as well.

Furthermore, it is possible to modify the signature with additional signature subpackets, e.g. custom annotations etc.

In order to create a delegation, e.g. in order to delegate trust to an OpenPGP CA, the user can do as follows:

PGPSecretKeyRing aliceKey = ...;
PGPPublicKeyRing caCertificate = ...;

CertificationResult result = PGPainless.certify()
        .certificate(caCertificate,
                Trustworthiness.fullyTrusted().introducer())
        .withKey(aliceKey, protector)
        .buildWithSubpackets(new CertificationSubpackets.Callback() {
            @Override
            public void modifyHashedSubpackets(
                    CertificationSubpackets hashedSubpackets) {
                hashedSubpackets.setRegularExpression(
                        "<[^>]+[@.]pgpainless\.org>$");
            }
        });

PGPSignature delegation = result.getCertification();
// or...
PGPPublicKeyRing delegatedCertificate = result.getCertifiedCertificate();

Here, Alice decided to delegate to the CA as a fully trusted introducer, meaning Alice will trust certificates that were certified by the CA.

Depending on the use-case, it is advisable to scope the delegation, e.g. to a specific domain by adding a Regex packet to the signature, as seen above. As a result, Alice will only trust those certificates introduced by the CA, which have a user-id matching the regex. This is optional however.

Check out PGPainless and don’t forget to check out the awesome NLnet foundation!


5 responses to “Creating a Web-of-Trust Implementation: Certify Keys with PGPainless”

Leave a Reply

Your email address will not be published. Required fields are marked *