Module sequoia_openpgp::cert::amalgamation::key

source ·
Expand description

Keys, their associated signatures, and some useful methods.

A KeyAmalgamation is similar to a ComponentAmalgamation, but a KeyAmalgamation includes some additional functionality that is needed to correctly implement a Key component’s semantics. In particular, unlike other components where the binding signature stores the component’s meta-data, a Primary Key doesn’t have a binding signature (it is the thing that other components are bound to!), and, as a consequence, the associated meta-data is stored elsewhere.

Unfortunately, a primary Key’s meta-data is usually not stored on a direct key signature, which would be convenient as it is located at the same place as a binding signature would be, but on the primary User ID’s binding signature. This requires some acrobatics on the implementation side to realize the correct semantics. In particular, a Key needs to memorize its role (i.e., whether it is a primary key or a subkey) in order to know whether to consider its own self signatures or the primary User ID’s self signatures when looking for its meta-data.

Ideally, a KeyAmalgamation‘s role would be encoded in its type. This increases safety, and reduces the run-time overhead. However, we want Cert::keys to return an iterator over all keys; we don’t want the user to have to specially handle the primary key when that fact is not relevant. This means that Cert::keys has to erase the returned Keys’ roles: all items in an iterator must have the same type. To support this, we have to keep track of a KeyAmalgamation’s role at run-time.

But, just because we need to erase a KeyAmalgamation’s role to implement Cert::keys doesn’t mean that we have to always erase it. To achieve this, we use three data types: PrimaryKeyAmalgamation, SubordinateKeyAmalgamation, and ErasedKeyAmalgamation. The first two encode the role information in their type, and the last one stores it at run time. We provide conversion functions to convert the static type information into dynamic type information, and vice versa.

Note: KeyBundles and KeyAmalgamations have a notable difference: whereas a KeyBundle’s role is a marker, a KeyAmalgamation’s role determines its semantics. A consequence of this is that it is not possible to convert a PrimaryKeyAmalgamation into a SubordinateAmalgamations, or vice versa even though we support changing a KeyBundle’s role:

// This works:
cert.primary_key().bundle().role_as_subordinate();

// But this doesn't:
let ka: ErasedKeyAmalgamation<_> = cert.keys().nth(0).expect("primary key");
let ka: openpgp::Result<SubordinateKeyAmalgamation<key::PublicParts>> = ka.try_into();
assert!(ka.is_err());

The use of the prefix Erased instead of Unspecified (cf. KeyRole::UnspecifiedRole) emphasizes this.

§Selecting Keys

It is essential to choose the right keys, and to make sure that they are appropriate. Below, we present some guidelines for the most common situations.

§Encrypting and Signing Messages

As a general rule of thumb, when encrypting or signing a message, you want to use keys that are alive, not revoked, and have the appropriate capabilities right now. For example, the following code shows how to find a key, which is appropriate for signing a message:

use openpgp::types::RevocationStatus;
use sequoia_openpgp::policy::StandardPolicy;

let p = &StandardPolicy::new();

let cert = cert.with_policy(p, None)?;

if let RevocationStatus::Revoked(_) = cert.revocation_status() {
    // The certificate is revoked, don't use any keys from it.
} else if let Err(_) = cert.alive() {
    // The certificate is not alive, don't use any keys from it.
} else {
    for ka in cert.keys() {
        if let RevocationStatus::Revoked(_) = ka.revocation_status() {
            // The key is revoked.
        } else if let Err(_) = ka.alive() {
            // The key is not alive.
        } else if ! ka.for_signing() {
            // The key is not signing capable.
        } else {
            // Use it!
        }
    }
}

§Verifying a Message

When verifying a message, you only want to use keys that were alive, not revoked, and signing capable when the message was signed. These are the keys that the signer would have used, and they reflect the signer’s policy when they made the signature. (See the Policy discussion for an explanation.)

For version 4 Signature packets, the Signature Creation Time subpacket indicates when the signature was allegedly created. For the purpose of finding the key to verify the signature, this time stamp should be trusted: if the key is authenticated and the signature is valid, then the time stamp is valid; if the signature is not valid, then forging the time stamp won’t help an attacker.

use openpgp::types::RevocationStatus;
use sequoia_openpgp::policy::StandardPolicy;

let p = &StandardPolicy::new();

let cert = cert.with_policy(p, timestamp)?;
if let RevocationStatus::Revoked(_) = cert.revocation_status() {
    // The certificate is revoked, don't use any keys from it.
} else if let Err(_) = cert.alive() {
    // The certificate is not alive, don't use any keys from it.
} else {
    for ka in cert.keys().key_handle(issuer) {
        if let RevocationStatus::Revoked(_) = ka.revocation_status() {
            // The key is revoked, don't use it!
        } else if let Err(_) = ka.alive() {
            // The key was not alive when the signature was made!
            // Something fishy is going on.
        } else if ! ka.for_signing() {
            // The key was not signing capable!  Better be safe
            // than sorry.
        } else {
            // Try verifying the message with this key.
        }
    }
}

§Decrypting a Message

When decrypting a message, it seems like one ought to only use keys that were alive, not revoked, and encryption-capable when the message was encrypted. Unfortunately, we don’t know when a message was encrypted. But anyway, due to the slow propagation of revocation certificates, we can’t assume that senders won’t mistakenly use a revoked key.

However, wanting to decrypt a message encrypted using an expired or revoked key is reasonable. If someone is trying to decrypt a message using an expired key, then they are the certificate holder, and probably attempting to access archived data using a key that they themselves revoked! We don’t want to prevent that.

We do, however, want to check whether a key is really encryption capable. This discussion explains why using a signing key to decrypt a message can be dangerous. Since we need a binding signature to determine this, but we don’t have the time that the message was encrypted, we need a workaround. One approach would be to check whether the key is encryption capable now. Since a key’s key flags don’t typically change, this will correctly filter out keys that are not encryption capable. But, it will skip keys whose self signature has expired. But that is not a problem either: no one sets self signatures to expire; if anything, they set keys to expire. Thus, this will not result in incorrectly failing to decrypt messages in practice, and is a reasonable approach.

use sequoia_openpgp::policy::StandardPolicy;

let p = &StandardPolicy::new();

let decryption_keys = cert.keys().with_policy(p, None)
    .for_storage_encryption().for_transport_encryption()
    .collect::<Vec<_>>();

Structs§

Traits§

Type Aliases§