Modern encryption technologies for day-to-day use are based on public key algorithms. This reduces the problem of exchanging secret keys to the problem of (exchanging and) validating public keys, which are, as the name indicates, not secret. However, the challenge is that there is a priori no way to tell which public key belongs to who.

If you read about encryption, you get the feeling that there are two complementary approaches and implementations which are used in different areas.

  • On the one hand, there is a wealth of technology that I’d like to summarize with the term public key infrastructure or PKI. This area uses Certification Authorities (CAs) as ultimately trusted parties, which verify and sign other peoples public keys. PKI is based on X.509 certificates. The most common application is probably SSL/TLS for HTTPS. A popular command line tool (and programming library) is OpenSSL. For security purposes, the actual secret key handling can be moved to dedicated hardware modules.

  • On the other hand, there is OpenPGP (based on the original PGP) and its GnuPG implementation. Here, public keys are usually distributed via e-mail, web pages, and key servers. Verification of keys should happen via other communication channels, such as phone calls or face-to-face meeting. Public keys can be signed by anyone. Everyone is a point of entry into the web of trust.

Both technologies are based on the public key algorithms DSA and RSA. PKI also supports Diffie-Hellman key exchange protocols. Both technologies support the same block cipher algorithms, such as AES, and the same hash methods, such as SHA-256. Besides the technical differences of the file formats, the conceptual difference is the dominant role of Certification Authorities (CAs) in PKI.

The lack of CAs in OpenPGP can be seen as an advantage for OpenPGP. A Certification Authority is a potential attack target. The goal of a CA is that a large number of people trust the certificates issued by the CA. If an attacker is able to forge CA certificates, he can fool everyone who put trust in that compromised CA. Furthermore, until recently, it was very costly to obtain a certificate from a CA, for example, for a website (nowadays, there is Let’s Encrypt). So the entry barrier of people to use it for personal websites or personal email exchange was rather high.

The lack of CAs in OpenPGP can also be seen as a disadvantage. When you communicate with friends or coworkers, it shouldn’t be a problem to verify that they the private keys belonging to the public keys you have on your computer. You can, for example, compare the fingerprints in a face-to-face conversation. By doing this, you can build a web of trust. However, this solution does not scale well. How can you verify the key of someone who you have never met, and who has no links to you in the web of trust? How could you verify the public key of a website, if OpenPGP was used for HTTPS?

These are exactly this kind of problems that a CA solves. I’ve said that there is no such thing as CAs in OpenPGP. However, this is not true. We can use the concept of CAs in OpenPGP. The technical requirements are specified in the standard and implemented in GnuPG. The feature is called trust signatures. In my experience, this is a little known, little used, and sparsely documented feature. The main page of GnuPG (which hosts its manuals and user guides) has only three search results for “trust signature” at the time of this writing. Trust signatures, however, enables us to use the best of both worlds, and gives everyone to the freedom to set individual trust levels in the web of trust or to trust a CA which signs for the validity of other peoples keys. So let’s see how to

  1. Set up a tier 2 Certification Authority,
  2. Sign for the validity of other peoples keys as a Certification Authority,
  3. Trust a Certification Authority and
  4. Distributed signed keys.

1. Set up OpenPGP Certification Authority

In this article, we will work on a two-tier Certification Authority. This means we will first create a key for the Root CA. This will be the trusted entry point. Besides this, we will create a key for the Issuing CA. The idea behind this split is that we will use the key of the Issuing CA for the day-to-day work. This means we will sign all the client keys with the Issuing CA. The Root CA key should be locked away in a safe (literally) and used on a computer which is never connected to the internet. This structure is beneficial in case the private key of Issuing CA is compromised. In this unpleasant case, we can use the Root CA key and revoke the Issuing CA. If this happens, all the certificates issued by the Issuing CA will not be trusted anymore and we need to sign them again with a new Issuing CA. However, since the Root CA is still trusted, we don’t need to redistribute a new public key to the people who trust the CA.

There are many other possible CA structures which have been studied in the context of PKI, see Planning for PKI by Russ Housley and Tim Polk. Most of the arguments given for or against a certain structure in the context of PKI can also be applied to OpenPGP. The structure of our CA including the two clients Alice and Bob is shown in the following figure.

Tier 2 Certification Authority

So, let’s create the keys for the Root CA and the Issuing CA. There is nothing special about creating these keys.

For the Root CA, we have created the 4096 bit key 0x49FA0992 with the user id Root CA <root-ca@example.com>. The expiry date is chose 30 years in the future. This means that in 30 years, everyone who puts trust in the CA has to update the CA’s public key or its self-certificate.

[root-ca@root-ca-pc /]# gpg2 --list-sigs
------------------------
pub   4096R/49FA0992 2018-07-12 [expires: 2048-07-04]
uid                  Root CA <root-ca@example.com>
sig 3        49FA0992 2018-07-12  Root CA <root-ca@example.com>
sub   4096R/380D7080 2018-07-12 [expires: 2048-07-04]
sig          49FA0992 2018-07-12  Root CA <root-ca@example.com>
[root-ca@root-ca-pc /]# gpg2 --output root_ca.asc --armor \
  --export root-ca@example.com

For the Issuing CA, we have created the 4096 bit key 0x30BF09DD with the user id Issuing CA <issuing-ca@example.com>. The expiry date is also chose 30 years in the future. In this example, we create the issuing key on a different machine, to highlight the point that the secret key of the Root CA should be locked away in a safe.

[issuing-ca@issuing-ca-pc /]# gpg2 --list-sigs
------------------------
pub   4096R/30BF09DD 2018-07-12 [expires: 2048-07-04]
uid                  Issuing CA <issuing-ca@example.com>
sig 3        30BF09DD 2018-07-12  Issuing CA <issuing-ca@example.com>
sub   4096R/B3F14D6E 2018-07-12 [expires: 2048-07-04]
sig          30BF09DD 2018-07-12  Issuing CA <issuing-ca@example.com>
[issuing-ca@issuing-ca-pc /]# gpg2 --output issuing_ca.asc --armor \
  --export issuing-ca@example.com

The Issuing CA will create regular signatures for its clients. Regular signatures are trust signatures of depth zero. The Root CA has to create a trust signature for the Issuing CA of depth one. This signature extends the trust put in the Root CA to the Issuing CA. If you trust the Root CA, you also trust any signature issued by the Issuing CA.

The required trust signature is the created with:

[root-ca@root-ca-pc /]# gpg2 --import issuing_ca.asc
[root-ca@root-ca-pc /]# gpg2 --edit-key issuing-ca@example.com
gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


pub  4096R/30BF09DD  created: 2018-07-12  expires: 2048-07-04  usage: SC  
                     trust: unknown       validity: unknown
sub  4096R/B3F14D6E  created: 2018-07-12  expires: 2048-07-04  usage: E   
[ unknown] (1). Issuing CA <issuing-ca@example.com>

gpg> tsign

pub  4096R/30BF09DD  created: 2018-07-12  expires: 2048-07-04  usage: SC  
                     trust: unknown       validity: unknown
 Primary key fingerprint: FB75 87EE FD1D 9941 4703  1355 0674 42EB 30BF 09DD

     Issuing CA <issuing-ca@example.com>

This key is due to expire on 2048-07-04.
Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I trust marginally
  2 = I trust fully

Your selection? 2

Please enter the depth of this trust signature.
A depth greater than 1 allows the key you are signing to make
trust signatures on your behalf.

Your selection? 1

Please enter a domain to restrict this signature, or enter for none.

Your selection? <blank>

Are you sure that you want to sign this key with your
key "Root CA <root-ca@example.com>" (49FA0992)

Really sign? (y/N) y

You need a passphrase to unlock the secret key for
user: "Root CA <root-ca@example.com>"
4096-bit RSA key, ID 49FA0992, created 2018-07-12


gpg> save
[root-ca@root-ca-pc /]# gpg2 --list-sigs
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   1  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: depth: 1  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 1f, 0u
gpg: next trustdb check due at 2048-07-04
------------------------
pub   4096R/49FA0992 2018-07-12 [expires: 2048-07-04]
uid                  Root CA <root-ca@example.com>
sig 3        49FA0992 2018-07-12  Root CA <root-ca@example.com>
sub   4096R/380D7080 2018-07-12 [expires: 2048-07-04]
sig          49FA0992 2018-07-12  Root CA <root-ca@example.com>

pub   4096R/30BF09DD 2018-07-12 [expires: 2048-07-04]
uid                  Issuing CA <issuing-ca@example.com>
sig 3        30BF09DD 2018-07-12  Issuing CA <issuing-ca@example.com>
sig        1 49FA0992 2018-07-12  Root CA <root-ca@example.com>
sub   4096R/B3F14D6E 2018-07-12 [expires: 2048-07-04]
sig          30BF09DD 2018-07-12  Issuing CA <issuing-ca@example.com>
[root-ca@root-ca-pc /]# gpg2 --output issuing_ca_signed.asc --armor \
  --export issuing-ca@example.com

Please note, in the third-to-last line of the --list-sigs commands, we have the depth-one trust signature.

The issuing CA should then import the signature and the root key itself.

[issuing-ca@issuing-ca-pc /]# gpg2 --import root_ca.asc
[issuing-ca@issuing-ca-pc /]# gpg2 --import issuing_ca_signed.asc

This is it. Our CA is ready to sign other peoples public keys.

2. Sign requests

This is nothing else as signing a regular key. In fact, the Issuing CA is issuing regular signatures. As an example, we want to sign the key 0x56644B17 of Bob. Assume we have verified Bob’s identity and that he has the private key that belongs to this public key. First, we import the public key of bob at the Issuing CA.

Then we can issue a regular signature for Bob.

[issuing-ca@issuing-ca-pc /]# gpg2 --import bob.asc
[issuing-ca@issuing-ca-pc /]# gpg2 --sign-key bob@example.com

pub  2048R/56644B17  created: 2018-07-12  expires: 2019-07-12  usage: SC  
                     trust: unknown       validity: unknown
sub  2048R/C05477B2  created: 2018-07-12  expires: 2019-07-12  usage: E   
[ unknown] (1). Bob Fivechar <bob@example.com>


pub  2048R/56644B17  created: 2018-07-12  expires: 2019-07-12  usage: SC  
                     trust: unknown       validity: unknown
 Primary key fingerprint: DCE1 B755 4CA7 FE64 C030  4804 73FC D818 5664 4B17

     Bob Fivechar <bob@example.com>

This key is due to expire on 2019-07-12.
Are you sure that you want to sign this key with your
key "Issuing CA <issuing-ca@example.com>" (30BF09DD)

Really sign? (y/N) y

You need a passphrase to unlock the secret key for
user: "Issuing CA <issuing-ca@example.com>"
4096-bit RSA key, ID 30BF09DD, created 2018-07-12
[issuing-ca@issuing-ca-pc /]# gpg2 --output bob_signed.asc --armor \
    --export bob@example.com

We can send the exported public key with the new signature to Bob. He can then redistribute it to people who trust the CA.

3. Trust the Certification Authority

Let’s assume Alice with key 0xE1BBD497 wants to trust all the public keys signed by the CA. For this, she has to get the CA’s public key and make sure that it is valid.

Then she can create a trust signature of depth-two for the Root CA. This means, that she will trust depth-one trust signatures of the Root CA, and since the Issuing CA has a depth-one signature form the Root CA, Alice will also trust any public key signed by the Issuing CA.

[alice@alice-pc /]# gpg2 --import root_ca.asc
[alice@alice-pc /]# gpg2 --edit-key root-ca@example.com
gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


pub  4096R/49FA0992  created: 2018-07-12  expires: 2048-07-04  usage: SC  
                     trust: unknown       validity: unknown
sub  4096R/380D7080  created: 2018-07-12  expires: 2048-07-04  usage: E   
[ unknown] (1). Root CA <root-ca@example.com>

gpg> tsign

pub  4096R/49FA0992  created: 2018-07-12  expires: 2048-07-04  usage: SC  
                     trust: unknown       validity: unknown
 Primary key fingerprint: 69FF 242C 8E51 A4F4 F2BA  56EC 116F 4EE9 49FA 0992

     Root CA <root-ca@example.com>

This key is due to expire on 2048-07-04.
Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I trust marginally
  2 = I trust fully

Your selection? 2

Please enter the depth of this trust signature.
A depth greater than 1 allows the key you are signing to make
trust signatures on your behalf.

Your selection? 2

Please enter a domain to restrict this signature, or enter for none.

Your selection? <blank>

Are you sure that you want to sign this key with your
key "Alice <alice@example.com>" (E1BBD497)

Really sign? (y/N) y

You need a passphrase to unlock the secret key for
user: "Alice <alice@example.com>"
2048-bit RSA key, ID E1BBD497, created 2018-07-12

gpg> save

Instead of using tsign, Alice could also issue a local trust signature with ltsign. This prevents Alice from exporting this signature accidentally, for example, because she doesn’t want anyone to know that she trusts this CA. However, it might strengthen peoples trust in the CA, if a CA is publicly trusted by a lot of people, however, this is outside of the scope of OpenPGP’s trust model.

4. Distribute keys

Now let’s see what happens when Alice imports Bob’s public key with the Issuing CA signature.

[alice@alice-pc /]# gpg2 --import bob_signed.asc

Let’s check which signatures are presents for Bob’s key.

[alice@alice-pc /]# gpg2 --list-sigs bob@example.com
pub   2048R/56644B17 2018-07-12 [expires: 2019-07-12]
uid                  Bob Fivechar <bob@example.com>
sig 3        56644B17 2018-07-12  Bob Fivechar <bob@example.com>
sig          30BF09DD 2018-07-12  [User ID not found]
sub   2048R/C05477B2 2018-07-12 [expires: 2019-07-12]
sig          56644B17 2018-07-12  Bob Fivechar <bob@example.com>

We notice, that Alice doesn’t know about the Issuing CA. How does she know about the trust signature issued by the Root CA for the Issuing CA? Well, she doesn’t. If we check Bob’s key with --edit-key, we see that it’s trust and validity are unknown.

The problem is that GnuPG does not automatically transmit the all public keys in the trust chain. This means Bob has to hand out his public key bundled with the Issuing CA’s public key (and potentially the Root CA’s public key). For this purpose, the CA can simple provide Bob with a concatenation of all the exported public key required to get from his key to the Root CA. Bob should then always hand out this concatenation of all keys. If Bob knows the structure of the CA, he could also generate the exported full chain himself.

[bob@bob-pc /]# gpg2 --import root_ca.asc
[bob@bob-pc /]# gpg2 --import issuing_ca_signed.asc
[bob@bob-pc /]# gpg2 --output full_chain.asc --armor \
  --export root-ca@example.com issuing-ca@example.com bob@example.com

If Alice had imported the full chain of Root CA, Issuing CA and Bob’s key, the trust and validity values are as follows.

[alice@alice-pc /]# gpg2 --edit-key bob@example.com
gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


pub  2048R/56644B17  created: 2018-07-12  expires: 2019-07-12  usage: SC  
                     trust: unknown       validity: full
sub  2048R/C05477B2  created: 2018-07-12  expires: 2019-07-12  usage: E   
[  full  ] (1). Bob Fivechar <bob@example.com>

We still don’t trust Bob to verify other people’s keys, but we know that his public key is valid. So, we know that he holds the private key belonging to this public key.

Summary

This small set of instructions shows that it is possible to have Certification Authorities in the context of OpenPGP. In fact, this is not some exotic feature of the GnuPG implementation, it is part of the OpenPGP RFC4880 Internet standard.

The concept of ultimately trusted Certification Authorities might collide with the open, decentralized idea of OpenPGP. However, in practice, I think the larger clash is that people use OpenPGP for private communication and PKI for communication within their company. Trust signatures could be used to establish OpenPGP communication within a company. Companies could even make use of the more finely grained trust model of OpenPGP and employ a CA structure with marginally trusted CAs with trust only for a certain domain.