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 Signature
s, 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 ComponentAmalgamation
s
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§
- A certificate component, its associated data, and useful methods.
- An iterator over components.
- A
ComponentAmalgamation
plus aPolicy
and a reference time. - An iterator over valid components.
Traits§
- Methods for valid amalgamations.
- Embeds a policy and a reference time in an amalgamation.
Type Aliases§
- An Unknown component and its associated data.
- An iterator over
UnknownComponentAmalgamtion
s. - A User Attribute and its associated data.
- An iterator over
UserAttributeAmalgamtion
s. - A User ID and its associated data.
- An iterator over
UserIDAmalgamtion
s. - A Valid User Attribute and its associated data.
- An iterator over
ValidUserAttributeAmalgamtion
s. - A Valid User ID and its associated data.
- An iterator over
ValidUserIDAmalgamtion
s.