diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-02-20 20:29:04 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-02-21 12:57:24 +0100 |
commit | d34f521eee9ba50f92482d54b6e0af42296cd35d (patch) | |
tree | b6ae89ea687465aa9f4335f7f3206e926ea1aff1 | |
parent | 31514f6fddc8001906e0ba628cb772a9a54d2428 (diff) |
openpgp: Extend StandardPolicy to check for asymmetric algorithms.
- See #274.
-rw-r--r-- | openpgp/src/policy.rs | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/openpgp/src/policy.rs b/openpgp/src/policy.rs index d8971a48..55d3d72e 100644 --- a/openpgp/src/policy.rs +++ b/openpgp/src/policy.rs @@ -155,6 +155,9 @@ pub struct StandardPolicy { // Symmetric algorithms. symmetric_algos: SymmetricAlgorithmCutoffList, + + // Asymmetric algorithms. + asymmetric_algos: AsymmetricAlgorithmCutoffList, } impl Default for StandardPolicy { @@ -200,6 +203,28 @@ a_cutoff_list!(RevocationHashCutoffList, HashAlgorithm, 12, ACCEPT, // 11. SHA224 ]); +a_cutoff_list!(AsymmetricAlgorithmCutoffList, AsymmetricAlgorithm, 18, + [ + Some(Timestamp::Y2014), // 0. RSA1024. + ACCEPT, // 1. RSA2048. + ACCEPT, // 2. RSA3072. + ACCEPT, // 3. RSA4096. + Some(Timestamp::Y2014), // 4. ElGamal1024. + ACCEPT, // 5. ElGamal2048. + ACCEPT, // 6. ElGamal3072. + ACCEPT, // 7. ElGamal4096. + Some(Timestamp::Y2014), // 8. DSA1024. + ACCEPT, // 9. DSA2048. + ACCEPT, // 10. DSA3072. + ACCEPT, // 11. DSA4096. + ACCEPT, // 12. NistP256. + ACCEPT, // 13. NistP384. + ACCEPT, // 14. NistP521. + ACCEPT, // 15. BrainpoolP256. + ACCEPT, // 16. BrainpoolP512. + ACCEPT, // 17. Cv25519. + ]); + a_cutoff_list!(SymmetricAlgorithmCutoffList, SymmetricAlgorithm, 14, [ REJECT, // 0. Unencrypted. @@ -275,6 +300,7 @@ impl StandardPolicy { time: None, hash_algos_normal: NormalHashCutoffList::Default(), hash_algos_revocation: RevocationHashCutoffList::Default(), + asymmetric_algos: AsymmetricAlgorithmCutoffList::Default(), symmetric_algos: SymmetricAlgorithmCutoffList::Default(), packet_tags: PacketTagCutoffList::Default(), } @@ -411,6 +437,41 @@ impl StandardPolicy { } /// Always considers `s` to be secure. + pub fn accept_asymmetric_algo(&mut self, a: AsymmetricAlgorithm) { + self.asymmetric_algos.set(a, ACCEPT); + } + + /// Always considers `s` to be insecure. + pub fn reject_asymmetric_algo(&mut self, a: AsymmetricAlgorithm) { + self.asymmetric_algos.set(a, REJECT); + } + + /// 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]. + /// + /// [NIST Special Publication 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf + pub fn reject_asymmetric_algo_at<C>(&mut self, a: AsymmetricAlgorithm, + cutoff: C) + where C: Into<Option<SystemTime>>, + { + self.asymmetric_algos.set( + a, + cutoff.into().and_then(system_time_cutoff_to_timestamp)); + } + + /// Returns the cutoff times for the specified hash algorithm. + pub fn asymmetric_algo_cutoff(&self, a: AsymmetricAlgorithm) + -> Option<SystemTime> { + self.asymmetric_algos.cutoff(a).map(|t| t.into()) + } + + /// Always considers `s` to be secure. pub fn accept_symmetric_algo(&mut self, s: SymmetricAlgorithm) { self.symmetric_algos.set(s, ACCEPT); } @@ -520,6 +581,80 @@ impl Policy for StandardPolicy { Ok(()) } + fn key(&self, ka: &ValidKeyAmalgamation<key::PublicParts>) + -> Result<()> + { + use self::AsymmetricAlgorithm::{*, Unknown}; + use crate::types::PublicKeyAlgorithm::*; + use crate::crypto::mpis::PublicKey; + + #[allow(deprecated)] + let a = match (ka.pk_algo(), ka.mpis().bits()) { + // RSA. + (RSAEncryptSign, Some(b)) + | (RSAEncrypt, Some(b)) + | (RSASign, Some(b)) if b < 2048 => RSA1024, + (RSAEncryptSign, Some(b)) + | (RSAEncrypt, Some(b)) + | (RSASign, Some(b)) if b < 3072 => RSA2048, + (RSAEncryptSign, Some(b)) + | (RSAEncrypt, Some(b)) + | (RSASign, Some(b)) if b < 4096 => RSA3072, + (RSAEncryptSign, Some(_)) + | (RSAEncrypt, Some(_)) + | (RSASign, Some(_)) => RSA4096, + (RSAEncryptSign, None) + | (RSAEncrypt, None) + | (RSASign, None) => unreachable!(), + + // ElGamal. + (ElGamalEncryptSign, Some(b)) + | (ElGamalEncrypt, Some(b)) if b < 2048 => ElGamal1024, + (ElGamalEncryptSign, Some(b)) + | (ElGamalEncrypt, Some(b)) if b < 3072 => ElGamal2048, + (ElGamalEncryptSign, Some(b)) + | (ElGamalEncrypt, Some(b)) if b < 4096 => ElGamal3072, + (ElGamalEncryptSign, Some(_)) + | (ElGamalEncrypt, Some(_)) => ElGamal4096, + (ElGamalEncryptSign, None) + | (ElGamalEncrypt, None) => unreachable!(), + + // DSA. + (DSA, Some(b)) if b < 2048 => DSA1024, + (DSA, Some(b)) if b < 3072 => DSA2048, + (DSA, Some(b)) if b < 4096 => DSA3072, + (DSA, Some(_)) => DSA4096, + (DSA, None) => unreachable!(), + + // ECC. + (ECDH, _) | (ECDSA, _) | (EdDSA, _) => { + let curve = match ka.mpis() { + PublicKey::EdDSA { curve, .. } => curve, + PublicKey::ECDSA { curve, .. } => curve, + PublicKey::ECDH { curve, .. } => curve, + _ => unreachable!(), + }; + use crate::types::Curve; + match curve { + Curve::NistP256 => NistP256, + Curve::NistP384 => NistP384, + Curve::NistP521 => NistP521, + Curve::BrainpoolP256 => BrainpoolP256, + Curve::BrainpoolP512 => BrainpoolP512, + Curve::Ed25519 => Cv25519, + Curve::Cv25519 => Cv25519, + Curve::Unknown(_) => Unknown, + Curve::__Nonexhaustive => unreachable!(), + } + }, + + _ => Unknown, + }; + + let time = self.time.unwrap_or_else(Timestamp::now); + self.asymmetric_algos.check(a, time) + } + fn packet(&self, packet: &Packet) -> Result<()> { let time = self.time.unwrap_or_else(Timestamp::now); self.packet_tags.check(packet.tag(), time) @@ -531,6 +666,99 @@ impl Policy for StandardPolicy { } } +/// Asymmetric encryption algorithms. +/// +/// This type is for refining the [`StandardPolicy`] with respect to +/// asymmetric algorithms. In contrast to [`PublicKeyAlgorithm`], it +/// does not concern itself with the use (encryption or signing), and +/// it does include key sizes (if applicable) and elliptic curves. +/// +/// [`StandardPolicy`]: struct.StandardPolicy.html +/// [`PublicKeyAlgorithm`]: ../types/enum.PublicKeyAlgorithm.html +/// +/// Key sizes put into are buckets, rounding down to the nearest +/// bucket. For example, a 3253-bit RSA key is categorized as +/// `RSA3072`. +#[derive(Clone, Debug)] +pub enum AsymmetricAlgorithm { + /// RSA with key sizes up to 2048-1 bit. + RSA1024, + /// RSA with key sizes up to 3072-1 bit. + RSA2048, + /// RSA with key sizes up to 4096-1 bit. + RSA3072, + /// RSA with key sizes larger or equal to 4096 bit. + RSA4096, + /// ElGamal with key sizes up to 2048-1 bit. + ElGamal1024, + /// ElGamal with key sizes up to 3072-1 bit. + ElGamal2048, + /// ElGamal with key sizes up to 4096-1 bit. + ElGamal3072, + /// ElGamal with key sizes larger or equal to 4096 bit. + ElGamal4096, + /// DSA with key sizes up to 2048-1 bit. + DSA1024, + /// DSA with key sizes up to 3072-1 bit. + DSA2048, + /// DSA with key sizes up to 4096-1 bit. + DSA3072, + /// DSA with key sizes larger or equal to 4096 bit. + DSA4096, + /// NIST curve P-256. + NistP256, + /// NIST curve P-384. + NistP384, + /// NIST curve P-521. + NistP521, + /// brainpoolP256r1. + BrainpoolP256, + /// brainpoolP512r1. + BrainpoolP512, + /// D.J. Bernstein's Curve25519. + Cv25519, + /// Unknown algorithm. + Unknown, + + /// This marks this enum as non-exhaustive. Do not use this + /// variant. + #[doc(hidden)] __Nonexhaustive, +} + +impl std::fmt::Display for AsymmetricAlgorithm { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl From<AsymmetricAlgorithm> for u8 { + fn from(a: AsymmetricAlgorithm) -> Self { + use self::AsymmetricAlgorithm::*; + match a { + RSA1024 => 0, + RSA2048 => 1, + RSA3072 => 2, + RSA4096 => 3, + ElGamal1024 => 4, + ElGamal2048 => 5, + ElGamal3072 => 6, + ElGamal4096 => 7, + DSA1024 => 8, + DSA2048 => 9, + DSA3072 => 10, + DSA4096 => 11, + NistP256 => 12, + NistP384 => 13, + NistP521 => 14, + BrainpoolP256 => 15, + BrainpoolP512 => 16, + Cv25519 => 17, + Unknown => 255, + __Nonexhaustive => unreachable!(), + } + } +} + #[cfg(test)] mod test { use std::io::Read; @@ -1441,4 +1669,18 @@ mod test { = e.downcast().unwrap()), } } + + #[test] + fn reject_asymmetric_algos() -> Result<()> { + let cert = Cert::from_bytes(crate::tests::key("neal.pgp"))?; + let p = &mut P::new(); + let t = crate::frozen_time(); + + assert_eq!(cert.with_policy(p, t).keys().count(), 4); + p.reject_asymmetric_algo(AsymmetricAlgorithm::RSA1024); + assert_eq!(cert.with_policy(p, t).keys().count(), 4); + p.reject_asymmetric_algo(AsymmetricAlgorithm::RSA2048); + assert_eq!(cert.with_policy(p, t).keys().count(), 1); + Ok(()) + } } |