ENOSUCHBLOG

Programming, philosophy, pedaling.


Introducing pgpkeydump

Apr 14, 2023     Tags: cryptography, devblog, programming, rust    


This is a short tool announcement post for pgpkeydump, a CLI tool I’ve written as part of a yak stack of tasks related to bulk auditing of PGP signatures and their associated keys1.

TL;DR: You can use pgpkeydump to get a JSON representation of a PGP key, including relevant components like the key’s algorithm, algorithm parameters, expiry, and so forth:

1
2
3
4
cargo install pgpkeydump

pgpkeydump \
    <(curl https://keys.openpgp.org/vks/v1/by-keyid/85AE00C504833B3C)

produces (with some light editing for readability):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
  "armor_headers": [
    "46C3 9716 A8F0 7E98 384E  28F7 85AE 00C5 0483 3B3C",
    "William Woodruff (yossarian.net) <william@yossarian.net"
  ],
  "fingerprint": "46C39716A8F07E98384E28F785AE00C504833B3C",
  "keyid": "85AE00C504833B3C",
  "userids": [
    "William Woodruff (yossarian.net) <william@yossarian.net>"
  ],
  "primary_key": {
    "algorithm": "RSA",
    "parameters": {
      "algorithm": "RSA",
      "e": {
        "bitness": 17,
        "value": "010001"
      },
      "n": {
        "bitness": 4096,
        "value": "very-long-n"
      }
    },
    "fingerprint": "46C39716A8F07E98384E28F785AE00C504833B3C",
    "keyid": "85AE00C504833B3C",
    "expiration": "2025-05-23T16:56:46+00:00",
    "purposes": {
      "authentication": true,
      "certification": true,
      "signing": true,
      "storage_encryption": false,
      "transport_encryption": false
    }
  },
  "subkeys": [],
  "revocation_keys": [],
  "alive": true
}

Motivation

Despite2 being around for over 30 years, there is no good way to get a machine-readable dump of a PGP key (or, more generally, any sequence of PGP packets) using tools in the mainstream PGP ecosystem. This is particularly annoying when you want to do something extremely reasonable, like see how strong the average signing key for a particular task (such as package or code signing) is.

If you stay within the world of gpg and adjacent tools, your best options are:

There is no good technical reason why I should’t be able to get a basic JSON representation of a PGP key message (i.e., a primary key packet, subkey packets, and a few other related packets). This is why pgpkeydump now exists.

Implementation

Under the hood, pgpkeydump is just Sequoia’s Rust APIs, wrapped into serde Serialize-able structures. Everything it does is something that Sequoia could do on its own (either in APIs or via the sq CLI), but it was easier for me to make a standalone tool than to go through an upstreaming process for a single frame in my yak stack.

For example, this is all that pgpkeydump does to turn a Sequoia ValidKeyAmalgamation (i.e. a key that’s been bound to a time and policy) into a serializable structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#[derive(Serialize)]
struct DumpableKey {
    algorithm: String,
    parameters: DumpableKeyParams,
    fingerprint: String,
    keyid: String,
    expiration: Option<String>,
    purposes: KeyPurposes,
}

impl From<ValidKeyAmalgamation<'_, PublicParts, PrimaryRole, ()>> for DumpableKey {
    fn from(key: ValidKeyAmalgamation<'_, PublicParts, PrimaryRole, ()>) -> Self {
        let expiration = key
            .key_expiration_time()
            .map(|t| DateTime::<Utc>::from(t).to_rfc3339());
        let purposes = KeyPurposes {
            authentication: key.for_authentication(),
            certification: key.for_certification(),
            signing: key.for_signing(),
            storage_encryption: key.for_storage_encryption(),
            transport_encryption: key.for_transport_encryption(),
        };
        let key = key.key();

        Self {
            algorithm: key.pk_algo().to_string(),
            parameters: key.mpis().into(),
            fingerprint: key.fingerprint().to_hex(),
            keyid: key.keyid().to_hex(),
            expiration,
            purposes,
        }
    }
}

(There’s a bit more to it in terms of further nested structures and From implementations, but you get the idea.)


  1. Stay tuned for a separate blog post on those results! 

  2. Or perhaps because: PGP is a paleolithic ecosystem that has never made (meaningful) strides towards modernization of its tooling, formats, or design. Much has been written about this, so I’ll spare it for this post. 


Discussions: Reddit Mastodon Twitter