Struct sequoia_openpgp::policy::StandardPolicy

source ·
pub struct StandardPolicy<'a> { /* private fields */ }
Expand description

The standard policy.

The standard policy stores when each algorithm in a family of algorithms is no longer considered safe. Attempts to use an algorithm after its cutoff time should fail.

A StandardPolicy can be configured using Rust. Sometimes it is useful to configure it via a configuration file. This can be done using the sequoia-policy-config crate.

It is recommended to support using a configuration file when the program should respect the system’s crypto policy. This is required on Fedora, for instance. See the [Fedora Crypto Policies] project for more information.

When validating a signature, we normally want to know whether the algorithms used are safe now. That is, we don’t use the signature’s alleged creation time when considering whether an algorithm is safe, because if an algorithm is discovered to be compromised at time X, then an attacker could forge a message after time X with a signature creation time that is prior to X, which would be incorrectly accepted.

Occasionally, we know that a signature has not been tampered with since some time in the past. We might know this if the signature was stored on some tamper-proof medium. In those cases, it is reasonable to use the time that the signature was saved, since an attacker could not have taken advantage of any weaknesses found after that time.

§Examples

A StandardPolicy object can be used to build specialized policies. For example the following policy filters out Persona certifications mimicking what GnuPG does when calculating the Web of Trust.

use sequoia_openpgp as openpgp;
use std::io::{Cursor, Read};
use openpgp::Result;
use openpgp::packet::{Packet, Signature, key::PublicParts};
use openpgp::cert::prelude::*;
use openpgp::parse::Parse;
use openpgp::armor::{Reader, ReaderMode, Kind};
use openpgp::policy::{HashAlgoSecurity, Policy, StandardPolicy};
use openpgp::types::{
   SymmetricAlgorithm,
   AEADAlgorithm,
   SignatureType
};

#[derive(Debug)]
struct RejectPersonaCertificationsPolicy<'a>(StandardPolicy<'a>);

impl Policy for RejectPersonaCertificationsPolicy<'_> {
    fn key(&self, ka: &ValidErasedKeyAmalgamation<PublicParts>)
           -> Result<()>
    {
        self.0.key(ka)
    }

    fn signature(&self, sig: &Signature, sec: HashAlgoSecurity) -> Result<()> {
        if sig.typ() == SignatureType::PersonaCertification {
            Err(anyhow::anyhow!("Persona certifications are ignored."))
        } else {
            self.0.signature(sig, sec)
        }
    }

    fn symmetric_algorithm(&self, algo: SymmetricAlgorithm) -> Result<()> {
        self.0.symmetric_algorithm(algo)
    }

    fn aead_algorithm(&self, algo: AEADAlgorithm) -> Result<()> {
        self.0.aead_algorithm(algo)
    }

    fn packet(&self, packet: &Packet) -> Result<()> {
        self.0.packet(packet)
    }
}

impl RejectPersonaCertificationsPolicy<'_> {
    fn new() -> Self {
        Self(StandardPolicy::new())
    }
}

// this key has one persona certification
let data = r#"
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEX7JGrxYJKwYBBAHaRw8BAQdASKGcnowaZBDc2Z3rZZlWb6jEjne9sK76afbJ
trd5Uw+0BlRlc3QgMoiQBBMWCAA4FiEEyZ6oBYFia3z+ooCBqR9BqiGp8AQFAl+y
Rq8CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQqR9BqiGp8ASfxwEAvEb0
bFr7ZgFZSDOITNptm+FEynib8mmLACsvHAmCjvIA+gOaSNyxMW6N59q7/j0sDjp1
aYNgpNFLbYBZpkXXVL0GiHUEERYIAB0WIQTE4QfdkkisIbWVOcHmlsuS3dbWEwUC
X7JG4gAKCRDmlsuS3dbWExEwAQCpqfiVMhjDwVFMsMpwd5r0N/8rAx8/nmgpCsK3
M9TUrAD7BhTYVPRbkJqTZYd9DlLtBcbF3yNPTHlB+F2sFjI+cgo=
=ZfYu
-----END PGP PUBLIC KEY BLOCK-----
"#;

let mut cursor = Cursor::new(&data);
let mut reader = Reader::from_reader(&mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey)));

let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
let cert = Cert::from_bytes(&buf)?;

let ref sp = StandardPolicy::new();
let u = cert.with_policy(sp, None)?.userids().nth(0).unwrap();

// Under the standard policy the persona certification is visible.
assert_eq!(u.certifications().count(), 1);

// Under our custom policy the persona certification is not available.
let ref p = RejectPersonaCertificationsPolicy::new();
assert_eq!(u.with_policy(p, None)?.certifications().count(), 0);

Implementations§

source§

impl<'a> StandardPolicy<'a>

source

pub const fn new() -> Self

Instantiates a new StandardPolicy with the default parameters.

source

pub fn at<T>(time: T) -> Self
where T: Into<SystemTime>,

Instantiates a new StandardPolicy with parameters appropriate for time.

time is a meta-parameter that selects a security profile that is appropriate for the given point in time. When evaluating an object, the reference time should be set to the time that the object was stored to non-tamperable storage. Since most applications don’t record when they received an object, they should conservatively use the current time.

Note that the reference time is a security parameter and is different from the time that the object was allegedly created. Consider evaluating a signature whose Signature Creation Time subpacket indicates that it was created in 2007. Since the subpacket is under the control of the sender, setting the reference time according to the subpacket means that the sender chooses the security profile. If the sender were an attacker, she could have forged this to take advantage of security weaknesses found since 2007. This is why the reference time must be set—at the earliest—to the time that the message was stored to non-tamperable storage. When that is not available, the current time should be used.

source

pub fn time(&self) -> Option<SystemTime>

Returns the policy’s reference time.

The current time is None.

See StandardPolicy::at for details.

source

pub fn accept_hash(&mut self, h: HashAlgorithm)

Always considers h to be secure.

A cryptographic hash algorithm normally has three security properties:

  • Pre-image resistance,
  • Second pre-image resistance, and
  • Collision resistance.

A hash algorithm should only be unconditionally accepted if it has all three of these properties. See the documentation for HashAlgoSecurity for more details.

source

pub fn accept_hash_property(&mut self, h: HashAlgorithm, sec: HashAlgoSecurity)

Considers hash algorithm h to be secure for the specified security property sec.

For instance, an application may choose to allow an algorithm like SHA-1 in contexts like User ID binding signatures where only second preimage resistance is required but not in contexts like signatures over data where collision resistance is also required. Whereas SHA-1’s collision resistance is definitively broken, depending on the application’s threat model, it may be acceptable to continue to accept SHA-1 in these specific contexts.

source

pub fn reject_hash(&mut self, h: HashAlgorithm)

Considers h to be insecure in all security contexts.

A cryptographic hash algorithm normally has three security properties:

  • Pre-image resistance,
  • Second pre-image resistance, and
  • Collision resistance.

This method causes the hash algorithm to be considered unsafe in all security contexts.

See the documentation for HashAlgoSecurity for more details.

To express a more nuanced policy, use StandardPolicy::reject_hash_at or StandardPolicy::reject_hash_property_at.

source

pub fn reject_all_hashes(&mut self)

Considers all hash algorithms to be insecure.

Causes all hash algorithms to be considered insecure in all security contexts.

This is useful when using a good list to determine what algorithms are allowed.

source

pub fn reject_hash_at<T>(&mut self, h: HashAlgorithm, t: T)
where T: Into<Option<SystemTime>>,

Considers h to be insecure in all security contexts starting at time t.

A cryptographic hash algorithm normally has three security properties:

  • Pre-image resistance,
  • Second pre-image resistance, and
  • Collision resistance.

This method causes the hash algorithm to be considered unsafe in all security contexts starting at time t.

See the documentation for HashAlgoSecurity for more details.

To express a more nuanced policy, use StandardPolicy::reject_hash_property_at.

source

pub fn reject_hash_property_at<T>( &mut self, h: HashAlgorithm, sec: HashAlgoSecurity, t: T )
where T: Into<Option<SystemTime>>,

Considers h to be insecure starting at t for the specified security property.

A hash algorithm is considered secure if it has all of the following security properties:

  • Pre-image resistance,
  • Second pre-image resistance, and
  • Collision resistance.

Some contexts only require a subset of these security properties. Specifically, if an attacker is unable to influence the data that a user signs, then the hash algorithm only needs second pre-image resistance; it doesn’t need collision resistance. See the documentation for HashAlgoSecurity for more details.

This method makes it possible to specify different policies depending on the security requirements.

A cutoff of None means that there is no cutoff and the algorithm has no known vulnerabilities for the specified security policy.

As a rule of thumb, collision resistance is easier to attack than second pre-image resistance. And in practice there are practical attacks against several widely-used hash algorithms’ collision resistance, but only theoretical attacks against their second pre-image resistance. Nevertheless, once one property of a hash has been compromised, we want to deprecate its use as soon as it is feasible. Unfortunately, because OpenPGP certificates are long-lived, this can take years.

Given this, we start rejecting MD5 in cases where collision resistance is required in 1997 and completely reject it starting in 2004:

In 1996, Dobbertin announced a collision of the compression function of MD5 (Dobbertin, 1996). While this was not an attack on the full MD5 hash function, it was close enough for cryptographers to recommend switching to a replacement, such as SHA-1 or RIPEMD-160.

MD5CRK ended shortly after 17 August 2004, when collisions for the full MD5 were announced by Xiaoyun Wang, Dengguo Feng, Xuejia Lai, and Hongbo Yu. Their analytical attack was reported to take only one hour on an IBM p690 cluster.

(Accessed Feb. 2020.)

And we start rejecting SHA-1 in cases where collision resistance is required in 2013, and completely reject it in 2023:

Since 2005 SHA-1 has not been considered secure against well-funded opponents, as of 2010 many organizations have recommended its replacement. NIST formally deprecated use of SHA-1 in 2011 and disallowed its use for digital signatures in 2013. As of 2020, attacks against SHA-1 are as practical as against MD5; as such, it is recommended to remove SHA-1 from products as soon as possible and use instead SHA-256 or SHA-3. Replacing SHA-1 is urgent where it’s used for signatures.

(Accessed Feb. 2020.)

There are two main reasons why we have decided to accept SHA-1 for so long. First, as of the end of 2020, there are still a large number of certificates that rely on SHA-1. Second, Sequoia uses a variant of SHA-1 called SHA1CD, which is able to detect and mitigate the known attacks on SHA-1’s collision resistance.

Since RIPE-MD is structured similarly to SHA-1, we conservatively consider it to be broken as well. But, because it is not widely used in the OpenPGP ecosystem, we don’t make provisions for it.

Note: if a context indicates that it requires collision resistance, then it requires both collision resistance and second pre-image resistance, and both policies must indicate that the hash algorithm can be safely used at the specified time.

source

pub fn hash_cutoff( &self, h: HashAlgorithm, sec: HashAlgoSecurity ) -> Option<SystemTime>

Returns the cutoff time for the specified hash algorithm and security policy.

source

pub fn hash_revocation_tolerance<D>(&mut self, d: D)
where D: Into<Duration>,

Sets the amount of time to continue to accept revocation certificates after a hash algorithm should be rejected.

Using StandardPolicy::reject_hash_at, it is possible to indicate when a hash algorithm’s security has been compromised, and, as such, should no longer be accepted.

Applying this policy to revocation certificates can have some unfortunate side effects. In particular, if a certificate has been revoked using a revocation certificate that relies on a broken hash algorithm, but the most recent self signature uses a strong acceptable hash algorithm, then rejecting the revocation certificate would mean considering the certificate to not be revoked! This would be a catastrophe if the secret key material were compromised.

Unfortunately, this happens in practice. A common example appears to be a certificate that has been updated many times, and is then revoked using a revocation certificate that was generated when the certificate was generated.

Since the consequences of allowing an invalid revocation certificate are significantly less severe (a denial of service) than ignoring a valid revocation certificate (compromised confidentiality, integrity, and authentication), this option makes it possible to accept revocations using weak hash algorithms longer than other types of signatures.

By default, the standard policy accepts revocation certificates seven years after the hash they are using was initially compromised.

source

pub fn get_hash_revocation_tolerance(&self) -> Duration

Sets the amount of time to continue to accept revocation certificates after a hash algorithm should be rejected.

See StandardPolicy::hash_revocation_tolerance for details.

source

pub fn accept_critical_subpacket(&mut self, s: SubpacketTag)

Always considers s to be secure.

source

pub fn reject_critical_subpacket(&mut self, s: SubpacketTag)

Always considers s to be insecure.

source

pub fn reject_all_critical_subpackets(&mut self)

Considers all critical subpackets to be insecure.

This is useful when using a good list to determine what critical subpackets are allowed.

source

pub fn reject_critical_subpacket_at<C>(&mut self, s: SubpacketTag, cutoff: C)
where C: Into<Option<SystemTime>>,

Considers s to be insecure starting at cutoff.

A cutoff of None means that there is no cutoff and the subpacket has no known vulnerabilities.

By default, we accept all critical subpackets that Sequoia understands and honors.

source

pub fn critical_subpacket_cutoff(&self, s: SubpacketTag) -> Option<SystemTime>

Returns the cutoff times for the specified subpacket tag.

source

pub fn good_critical_notations(&mut self, good_list: &'a [&'a str])

Sets the list of accepted critical notations.

By default, we reject all critical notations.

source

pub fn accept_asymmetric_algo(&mut self, a: AsymmetricAlgorithm)

Always considers s to be secure.

source

pub fn reject_asymmetric_algo(&mut self, a: AsymmetricAlgorithm)

Always considers s to be insecure.

source

pub fn reject_all_asymmetric_algos(&mut self)

Considers all asymmetric algorithms to be insecure.

This is useful when using a good list to determine what algorithms are allowed.

source

pub fn reject_asymmetric_algo_at<C>( &mut self, a: AsymmetricAlgorithm, cutoff: C )
where C: Into<Option<SystemTime>>,

Considers a to be insecure starting at cutoff.

A cutoff of None means that there is no cutoff and the algorithm has no known vulnerabilities.

By default, we reject the use of asymmetric key sizes lower than 2048 bits starting in 2014 following NIST Special Publication 800-131A.

source

pub fn asymmetric_algo_cutoff( &self, a: AsymmetricAlgorithm ) -> Option<SystemTime>

Returns the cutoff times for the specified hash algorithm.

source

pub fn accept_symmetric_algo(&mut self, s: SymmetricAlgorithm)

Always considers s to be secure.

source

pub fn reject_symmetric_algo(&mut self, s: SymmetricAlgorithm)

Always considers s to be insecure.

source

pub fn reject_all_symmetric_algos(&mut self)

Considers all symmetric algorithms to be insecure.

This is useful when using a good list to determine what algorithms are allowed.

source

pub fn reject_symmetric_algo_at<C>(&mut self, s: SymmetricAlgorithm, cutoff: C)
where C: Into<Option<SystemTime>>,

Considers s to be insecure starting at cutoff.

A cutoff of None means that there is no cutoff and the algorithm has no known vulnerabilities.

By default, we reject the use of TripleDES (3DES) starting in the year 2017. While 3DES is still a “MUST implement” algorithm in RFC4880, released in 2007, there are plenty of other symmetric algorithms defined in RFC4880, and it says AES-128 SHOULD be implemented. Support for other algorithms in OpenPGP implementations is excellent. We chose 2017 as the cutoff year because NIST deprecated 3DES that year.

source

pub fn symmetric_algo_cutoff(&self, s: SymmetricAlgorithm) -> Option<SystemTime>

Returns the cutoff times for the specified hash algorithm.

source

pub fn accept_aead_algo(&mut self, a: AEADAlgorithm)

Always considers s to be secure.

This feature is experimental.

source

pub fn reject_aead_algo(&mut self, a: AEADAlgorithm)

Always considers s to be insecure.

This feature is experimental.

source

pub fn reject_all_aead_algos(&mut self)

Considers all AEAD algorithms to be insecure.

This is useful when using a good list to determine what algorithms are allowed.

source

pub fn reject_aead_algo_at<C>(&mut self, a: AEADAlgorithm, cutoff: C)
where C: Into<Option<SystemTime>>,

Considers a to be insecure starting at cutoff.

A cutoff of None means that there is no cutoff and the algorithm has no known vulnerabilities.

By default, we accept all AEAD modes.

This feature is experimental.

source

pub fn aead_algo_cutoff(&self, a: AEADAlgorithm) -> Option<SystemTime>

Returns the cutoff times for the specified hash algorithm.

This feature is experimental.

source

pub fn accept_packet_tag_version(&mut self, tag: Tag, version: u8)

Always accept the specified version of the packet.

If a packet does not have a version field, then its version is 0.

source

pub fn accept_packet_tag(&mut self, tag: Tag)

Always accept packets with the given tag independent of their version.

If you previously set a cutoff for a specific version of a packet, this overrides that.

source

pub fn reject_packet_tag_version(&mut self, tag: Tag, version: u8)

Always reject the specified version of the packet.

If a packet does not have a version field, then its version is 0.

source

pub fn reject_packet_tag(&mut self, tag: Tag)

Always reject packets with the given tag.

source

pub fn reject_all_packet_tags(&mut self)

Considers all packets to be insecure.

This is useful when using a good list to determine what packets are allowed.

source

pub fn reject_packet_tag_version_at<C>( &mut self, tag: Tag, version: u8, cutoff: C )
where C: Into<Option<SystemTime>>,

Start rejecting the specified version of packets with the given tag at t.

A cutoff of None means that there is no cutoff and the packet has no known vulnerabilities.

By default, we consider the Symmetrically Encrypted Data Packet (SED) insecure in messages created in the year 2004 or later. The rationale here is that Symmetrically Encrypted Integrity Protected Data Packet (SEIP) can be downgraded to SED packets, enabling attacks exploiting the malleability of the CFB stream (see EFAIL).

We chose 2004 as a cutoff-date because Debian 3.0 (Woody), released on 2002-07-19, was the first release of Debian to ship a version of GnuPG that emitted SEIP packets by default. The first version that emitted SEIP packets was GnuPG 1.0.3, released on 2000-09-18. Mid 2002 plus a 18 months grace period of people still using older versions is 2004.

source

pub fn reject_packet_tag_at<C>(&mut self, tag: Tag, cutoff: C)
where C: Into<Option<SystemTime>>,

Start rejecting packets with the given tag at t.

See the documentation for StandardPolicy::reject_packet_tag_version_at.

source

pub fn packet_tag_version_cutoff( &self, tag: Tag, version: u8 ) -> Option<SystemTime>

Returns the cutoff for the specified version of the specified packet tag.

This first considers the versioned cutoff list. If there is no entry in the versioned list, it fallsback to the unversioned cutoff list. If there is also no entry there, then it falls back to the default.

source

pub fn packet_tag_cutoff(&self, tag: Tag) -> Option<SystemTime>

👎Deprecated: Since 1.11. Use packet_tag_version_cutoff.

Returns the cutoff time for the specified packet tag.

This function returns the maximum cutoff for all versions of the packet. That is, if one version has a cutoff of t1, and another version has a cutoff of t2, this returns max(t1, t2). These semantics answer the question: “Up to which point can we use this packet?”

Trait Implementations§

source§

impl<'a> Clone for StandardPolicy<'a>

source§

fn clone(&self) -> StandardPolicy<'a>

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<'a> Debug for StandardPolicy<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Default for StandardPolicy<'a>

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl<'a> From<&'a StandardPolicy<'a>> for Option<&'a dyn Policy>

source§

fn from(p: &'a StandardPolicy<'a>) -> Self

Converts to this type from the input type.
source§

impl<'a> Policy for StandardPolicy<'a>

source§

fn signature(&self, sig: &Signature, sec: HashAlgoSecurity) -> Result<()>

Returns an error if the signature violates the policy. Read more
source§

fn key(&self, ka: &ValidErasedKeyAmalgamation<'_, PublicParts>) -> Result<()>

Returns an error if the key violates the policy. Read more
source§

fn packet(&self, packet: &Packet) -> Result<()>

Returns an error if the packet violates the policy. Read more
source§

fn symmetric_algorithm(&self, algo: SymmetricAlgorithm) -> Result<()>

Returns an error if the symmetric encryption algorithm violates the policy. Read more
source§

fn aead_algorithm(&self, algo: AEADAlgorithm) -> Result<()>

Returns an error if the AEAD mode violates the policy. Read more

Auto Trait Implementations§

§

impl<'a> Freeze for StandardPolicy<'a>

§

impl<'a> RefUnwindSafe for StandardPolicy<'a>

§

impl<'a> Send for StandardPolicy<'a>

§

impl<'a> Sync for StandardPolicy<'a>

§

impl<'a> Unpin for StandardPolicy<'a>

§

impl<'a> UnwindSafe for StandardPolicy<'a>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> DynClone for T
where T: Clone,

source§

fn __clone_box(&self, _: Private) -> *mut ()

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> Same for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.