Module sequoia_openpgp::cert::amalgamation

source ·
Expand description

Components, their associated signatures, and some useful methods.

Whereas a ComponentBundle owns a Component and its associated Signatures, a ComponentAmalgamation references a ComponentBundle and its containing Cert. This additional context means that a ComponentAmalgamation can implement more of OpenPGP’s high-level semantics than a ComponentBundle can. For instance, most of the information about a primary key, such as its capabilities, is on the primary User ID’s binding signature. A ComponentAmalgamation can find the certificate’s primary User ID; a ComponentBundle can’t. Similarly, when looking up a subpacket, if it isn’t present in the component’s binding signature, then an OpenPGP implementation is supposed to consult the certificate’s direct key signatures. A ComponentAmalgamation has access to this information; a ComponentBundle doesn’t.

Given the limitations of a ComponentBundle, it would seem more useful to just change it to include a reference to its containing certificate. That change would make ComponentAmalgamations redundant. Unfortunately, this isn’t possible, because it would result in a self-referential data structure, which Rust doesn’t allow. To understand how this arises, consider a certificate C, which contains a ComponentBundle B. If B contains a reference to C, then C references itself, because C contains B!

Cert:[ Bundle:[ &Cert ] ]
     ^            |
     `------------'

§Policy

Although a ComponentAmalgamation contains the information necessary to realize high-level OpenPGP functionality, components can have multiple self signatures, and functions that consult the binding signature need to determine the best one to use. There are two main concerns here.

First, we need to protect the user from forgeries. As attacks improve, cryptographic algorithms that were once considered secure now provide insufficient security margins. For instance, in 2007 it was possible to find MD5 collisions using just a few seconds of computing time on a desktop computer. Sequoia provides a flexible mechanism, called Policy objects, that allow users to implement this type of filtering: before a self signature is used, a policy object is queried to determine whether the Signature should be rejected. If so, then it is skipped.

Second, we need an algorithm to determine the most appropriate self signature. Obvious non-candidate self signatures are self signatures whose creation time is in the future. We don’t assume that these self signatures are bad per se, but that they represent a policy that should go into effect some time in the future.

We extend this idea of a self signature representing a policy for a certain period of time to all self signatures. In particular, Sequoia takes the view that a binding signature represents a policy that is valid from its creation time until its expiry. Thus, when considering what self signature to use, we need a reference time. Given the reference time, we then use the self signature that was in effect at that time, i.e., the most recent, non-expired, non-revoked self signature that was created at or prior to the reference time. In other words, we ignore self signatures created after the reference time. We take the position that if the certificate holder wants a new policy to apply to existing signatures, then the new self signature should be backdated, and existing self signatures revoked, if necessary.

Consider evaluating a signature over a document. Sequoia’s streaming verifier uses the signature’s creation time as the reference time. Thus, if the signature was created on June 9th, 2011, then, when evaluating that signature, the streaming verifier uses a self signature that was live at that time, since that was the self signature that represented the signer’s policy at the time the signature over the document was created.

A consequence of this approach is that even if the self signature were considered expired at the time the signature was evaluated (e.g., “now”), this fact doesn’t invalidate the signature. That is, a self signature’s lifetime does not impact a signature’s lifetime; a signature’s lifetime is defined by its own creation time and expiry. Similarly, a key’s lifetime is defined by its own creation time and expiry.

This interpretation of lifetimes removes a major disadvantage that comes with fast rotation of subkeys: if an implementation binds the lifetime of signatures to the signing key, and the key expires, then old signatures are considered invalid. Consider a user who generates a new signature subkey each week, and sets it to expire after exactly one week. If we use the policy that the signature is only valid while the key and the self signature are live, then if someone checks the signature a week after receiving it, the signature will be considered invalid, because the key has expired. The practical result is that all old messages from this user will be considered invalid! Unfortunately, this will result in users becoming accustomed to seeing invalid signatures, and cause them to be less suspcious of them.

Sequoia’s low-level mechanisms support this interpretation of self signatures, but they do not enforce it. It is still possible to realize other policies using this low-level API.

The possibility of abuse of this interpretation of signature lifetimes is limited. If a key has been compromised, then the right thing to do is to revoke it. Expiry doesn’t help: the attacker can simply create self-signatures that say whatever she wants. Assuming the secret key material has not been compromised, then an attacker could still reuse a message that would otherwise be considered expired. However, the attacker will not be able to change the signature’s creation time, so, assuming a mail context and MUAs that check that the time in the message’s headers matches the signature’s creation time, the mails will appear old. Further, this type of attack will be mitigated by the proposed “Intended Recipients” subpacket, which more tightly binds the message to its context.

§ValidComponentAmalgamation

Most operations need to query a ComponentAmalgamation for multiple pieces of information. Accidentally using a different Policy or a different reference time for one of the queries is easy, especially when the queries are spread across multiple functions. Further, using None for the reference time can result in subtle timing bugs as each function translates it to the current time on demand. In these cases, the correct approach would be for the user of the library to get the current time at the start of the operation. But, this is less convenient. Finally, passing a Policy and a reference time to most function calls clutters the code.

To mitigate these issues, we have a separate data structure, ValidComponentAmalgamation, which combines a ComponetAmalgamation, a Policy and a reference time. It implements methods that require a Policy and reference time, but instead of requiring the caller to pass them in, it uses the ones embedded in the data structure. Further, when the ValidComponentAmalgamation constructor is passed None for the reference time, it eagerly stores the current time, and uses that for all operations. This approach elegantly solves all of the aforementioned problems.

§Lifetimes

ComponentAmalgamation autoderefs to ComponentBundle. Unfortunately, due to the definition of the Deref trait, ComponentBundle is assigned the same lifetime as ComponentAmalgamation. However, it’s lifetime is actually 'a. Particularly when using combinators like std::iter::map, the ComponentBundle’s lifetime is longer. Consider the following code, which doesn’t compile:

use openpgp::cert::prelude::*;
use openpgp::packet::prelude::*;

cert.userids()
    .map(|ua| {
        // Use auto deref to get the containing `&ComponentBundle`.
        let b: &ComponentBundle<_> = &ua;
        b
    })
    .collect::<Vec<&UserID>>();

Compiling it results in the following error:

b returns a value referencing data owned by the current function

This error occurs because the Deref trait says that the lifetime of the target, i.e., &ComponentBundle, is bounded by ua’s lifetime, whose lifetime is indeed limited to the closure. But, &ComponentBundle is independent of ua; it is a copy of the ComponentAmalgamation’s reference to the ComponentBundle whose lifetime is 'a! Unfortunately, this can’t be expressed using Deref. But, it can be done using separate methods as shown below for the ComponentAmalgamation::component method:

use openpgp::cert::prelude::*;
use openpgp::packet::prelude::*;

cert.userids()
    .map(|ua| {
        // ua's lifetime is this closure.  But `component()`
        // returns a reference whose lifetime is that of
        // `cert`.
        ua.component()
    })
    .collect::<Vec<&UserID>>();

Modules§

  • Keys, their associated signatures, and some useful methods.

Structs§

Traits§

Type Aliases§