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 Key
s’ 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: KeyBundle
s and KeyAmalgamation
s 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 SubordinateAmalgamation
s, 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§
- A key, and its associated data, and useful methods.
- An iterator over
Key
s. - A
KeyAmalgamation
plus aPolicy
and a reference time. - An iterator over valid
Key
s.
Traits§
- Whether the key is a primary key.
Type Aliases§
- An amalgamation whose role is not known at compile time.
- A primary key amalgamation.
- A subordinate key amalgamation.
- A valid key whose role is not known at compile time.
- A Valid primary Key, and its associated data.
- A Valid subkey, and its associated data.