Exploring Openpgp

Let’s see how we can work with OpenPGP data on a low level. Again we start with an armored blob, but instead of merely printing it, we assume that it is an OpenPGP message, and try to parse it:

#[macro_use] // For armored!
extern crate openpgp;

fn main() {
    let mut reader = armored!(
        "-----BEGIN PGP PUBLIC KEY BLOCK-----

         mQENBFpxtsABCADZcBa1Q3ZLZnju18o0+t8LoQuIIeyeUQ0H45y6xUqyrD5HSkVM
         VGQs6IHLq70mAizBJ4VznUVqVOh/NhOlapXi6/TKpjHvttdg45o6Pgqa0Kx64luT
         ZY+TEKyILcdBdhr3CzsEILnQst5jadgMvU9fnT/EkJIvxtWPlUzU5R7nnALO626x
         2M5Pj3k0h3ZNHMmYQQtReX/RP/xUh2SfOYG6i/MCclIlee8BXHB9k0bW2NAX2W7H
         rLDGPm1LzmyqxFGDvDvfPlYZ5nN2cbGsv3w75LDzv75kMhVnkZsrUjnHjVRzFq7q
         fSIpxlvJMEMKSIJ/TFztQoOBO5OlBb5qzYPpABEBAAG0F+G8iM+BzrnPg8+Ezr/P
         hM6tzrvOt8+CiQFUBBMBCAA+FiEEfcpYtU6xQxad3uFfJH9tq8hJFP4FAlpxtsAC
         GwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQJH9tq8hJFP49hgf+
         IKvec0RkD9EHSLFc6AKDm/knaI4AIH0isZTz9jRCF8H/j3h8QVUE+/0jtCcyvR6F
         TGVSfO3pelDPYGIjDFI3aA6H/UlhZWzYRXZ+QQRrV0zwvLna3XjiW8ib3Ky+5bpQ
         0uVeee30u+U3SnaCL9QB4+UvwVvAxRuk49Z0Q8TsRrQyQNYpeZDN7uNrvA134cf6
         6pLUvzPG4lMLIvSXFuHou704EhT7NS3wAzFtjMrsLLieVqtbEi/kBaJTQSZQwjVB
         sE/Z8lp1heKw/33Br3cB63n4cTf0FdoFywDBhCAMU7fKboU5xBpm5bQJ4ck6j6w+
         BKG1FiQRR6PCUeb6GjxVOrkBDQRacbbAAQgAw538MMb/pRdpt7PTgBCedw+rU9fh
         onZYKwmCO7wz5VrVf8zIVvWKxhX6fBTSAy8mxaYbeL/3woQ9Leuo8f0PQNs9zw1N
         mdH+cnm2KQmL9l7/HQKMLgEAu/0C/q7ii/j8OMYitaMUyrwy+OzW3nCal/uJHIfj
         bdKx29MbKgF/zaBs8mhTvf/Tu0rIVNDPEicwijDEolGSGebZxdGdHJA31uayMHDK
         /mwySJViMZ8b+Lzc/dRgNbQoY6yjsjso7U9OZpQK1fooHOSQS6iLsSSsZLcGPD+7
         m7j3jwq68SIJPMsu0O8hdjFWL4Cfj815CwptAxRGkp00CIusAabO7m8DzwARAQAB
         iQE2BBgBCAAgFiEEfcpYtU6xQxad3uFfJH9tq8hJFP4FAlpxtsACGwwACgkQJH9t
         q8hJFP5rmQgAoYOUXolTiQmWipJTdMG/VZ5X7mL8JiBWAQ11K1o01cZCMlziyHnJ
         xJ6Mqjb6wAFpYBtqysJG/vfjc/XEoKgfFs7+zcuEnt41xJQ6tl/L0VTxs+tEwjZu
         Rp/owB9GCkqN9+xNEnlH77TLW1UisW+l0F8CJ2WFOj4lk9rcXcLlEdGmXfWIlVCb
         2/o0DD+HDNsF8nWHpDEy0mcajkgIUTvXQaDXKbccX6Wgep8dyBP7YucGmRPd9Z6H
         bGeT3KvlJlH5kthQ9shsmT14gYwGMR6rKpNUXmlpetkjqUK7pGVaHGgJWUZ9QPGU
         awwPdWWvZSyXJAPZ9lC5sTKwMJDwIxILug==
         =lAie
         -----END PGP PUBLIC KEY BLOCK-----"
    );

    // Parse message.
    let message = openpgp::Message::from_reader(&mut reader).unwrap();

    // Iterate over children.
    for (i, p) in message.children().enumerate() {
        println!("{}: {:?}", i, p);
    }
}

So we start with a reader, and parse it using Message::from_reader, which may fail, hence the unwrap(). We then get an iterator over the packets the message consists of, and enumerate them:

0: PublicKey(Key { version: 4, creation_time: 1517401792, pk_algo: 1, mpis: "263 bytes" })
1: UserID(UserID { value: "Ἀριστοτέλης" })
2: Signature(Signature { version: 4, sigtype: 19, issuer: "7DCA 58B5 4EB1 4316 9DDE  E15F 247F 6DAB C849 14FE", pk_algo: 1, hash_algo: 8, hashed_area: {Some(IssuerFingerprint): SubpacketArea { critical: false, tag: 33, value (21 bytes): [4, 125, 202, 88, 181, 78, 177, 67, 22, 157, 222, 225, 95, 36, 127, 109] }, Some(SignatureCreationTime): SubpacketArea { critical: false, tag: 2, value (4 bytes): [90, 113, 182, 192] }, Some(KeyFlags): SubpacketArea { critical: false, tag: 27, value (1 bytes): [3] }, Some(KeyExpirationTime): SubpacketArea { critical: false, tag: 9, value (4 bytes): [3, 194, 103, 0] }, Some(PreferredSymmetricAlgorithms): SubpacketArea { critical: false, tag: 11, value (4 bytes): [9, 8, 7, 2] }, Some(PreferredHashAlgorithms): SubpacketArea { critical: false, tag: 21, value (5 bytes): [8, 9, 10, 11, 2] }, Some(PreferredCompressionAlgorithms): SubpacketArea { critical: false, tag: 22, value (3 bytes): [2, 3, 1] }, Some(Features): SubpacketArea { critical: false, tag: 30, value (1 bytes): [1] }, Some(KeyServerPreferences): SubpacketArea { critical: false, tag: 23, value (1 bytes): [128] }}, unhashed_area: {Some(Issuer): SubpacketArea { critical: false, tag: 16, value (8 bytes): [36, 127, 109, 171, 200, 73, 20, 254] }}, hash_prefix: [61, 134], mpis: "258 bytes" })
3: PublicSubkey(Key { version: 4, creation_time: 1517401792, pk_algo: 1, mpis: "263 bytes" })
4: Signature(Signature { version: 4, sigtype: 24, issuer: "7DCA 58B5 4EB1 4316 9DDE  E15F 247F 6DAB C849 14FE", pk_algo: 1, hash_algo: 8, hashed_area: {Some(IssuerFingerprint): SubpacketArea { critical: false, tag: 33, value (21 bytes): [4, 125, 202, 88, 181, 78, 177, 67, 22, 157, 222, 225, 95, 36, 127, 109] }, Some(SignatureCreationTime): SubpacketArea { critical: false, tag: 2, value (4 bytes): [90, 113, 182, 192] }, Some(KeyFlags): SubpacketArea { critical: false, tag: 27, value (1 bytes): [12] }}, unhashed_area: {Some(Issuer): SubpacketArea { critical: false, tag: 16, value (8 bytes): [36, 127, 109, 171, 200, 73, 20, 254] }}, hash_prefix: [107, 153], mpis: "258 bytes" })

You are seeing a typical OpenPGP key, consisting of one public key packet, one userid packet and one public subkey packet, each with a signature binding the uid and subkey to the master key.

We are iterating over openpgp::Packets here. This is a Rust-style enumeration that directly corresponds to the packet types defined by Section 5 of RFC 4880. “Inside” each enumeration value is a struct holding the actual data. Note how e.g. openpgp::Packet::PublicKey and openpgp::Packet::PublicSubkey share the same Key struct.

While this is certainly interesting, it is hardly a practical interface. Let’s parse the message so that we get a transferable public key instead:

    // Parse into TPK.
    let tpk = openpgp::TPK::from_message(message).unwrap();
    println!("Fingerprint: {}", tpk.fingerprint());

    // List userids.
    for (i, u) in tpk.userids().enumerate() {
        println!("{}: UID: {}, {} self-signature(s), {} certification(s)",
                 i, u.userid(),
                 u.selfsigs().count(),
                 u.certifications().count());
    }

    // List subkeys.
    for (i, s) in tpk.subkeys().enumerate() {
        println!("{}: Fingerprint: {}, {} self-signature(s), {} certification(s)",
                 i, s.subkey().fingerprint(),
                 s.selfsigs().count(),
                 s.certifications().count());
    }

Running this prints:

Fingerprint: 7DCA 58B5 4EB1 4316 9DDE  E15F 247F 6DAB C849 14FE
0: UID: Ἀριστοτέλης, 1 self-signature(s), 0 certification(s)
0: Fingerprint: F9F3 9E3D F22A 2B8A 4399  2E83 EA6E 3770 628A 713C, 1 self-signature(s), 0 certification(s)

Transferable public keys can be used to verify signatures, and encrypt data. They can be stored in a keystore and uploaded to keyservers.