diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-02-20 16:57:38 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-02-20 17:03:08 +0100 |
commit | f8d10cf720697be099ef1e9a3d72462e50e82f28 (patch) | |
tree | 5be194aa05ac41ba4b685f4b60ea5b3dbfe868c0 | |
parent | 8bb6ca2b4a99cc2824c7fb6d8f34d8086e99e852 (diff) |
openpgp: Extend policy to check symmetric algorithms.
- See #274.
-rw-r--r-- | openpgp/src/parse/stream.rs | 6 | ||||
-rw-r--r-- | openpgp/src/policy.rs | 139 |
2 files changed, 141 insertions, 4 deletions
diff --git a/openpgp/src/parse/stream.rs b/openpgp/src/parse/stream.rs index 646da1fe..2d44d9bb 100644 --- a/openpgp/src/parse/stream.rs +++ b/openpgp/src/parse/stream.rs @@ -1325,8 +1325,12 @@ impl<'a, H: VerificationHelper + DecryptionHelper> Decryptor<'a, H> { .into()); } + let sym_algo = + sym_algo.expect("if we got here, sym_algo is set"); + v.policy.symmetric_algorithm(sym_algo)?; + v.structure.new_encryption_layer( - sym_algo.expect("if we got here, sym_algo is set"), + sym_algo, if let Packet::AED(ref p) = pp.packet { Some(p.aead()) } else { diff --git a/openpgp/src/policy.rs b/openpgp/src/policy.rs index c2576e43..e55d3ba5 100644 --- a/openpgp/src/policy.rs +++ b/openpgp/src/policy.rs @@ -40,9 +40,12 @@ use crate::{ Tag, }, Result, - types::HashAlgorithm, - types::SignatureType, - types::Timestamp, + types::{ + HashAlgorithm, + SignatureType, + SymmetricAlgorithm, + Timestamp, + }, }; #[macro_use] mod cutofflist; @@ -93,6 +96,18 @@ pub trait Policy : fmt::Debug { Ok(()) } + /// Returns an error if the symmetric encryption algorithm + /// violates the policy. + /// + /// This function performs the last check before an encryption + /// container is decrypted by the streaming decryptor. + /// + /// With this function, you can prevent the use of insecure + /// symmetric encryption algorithms. + fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { + Ok(()) + } + /// Returns an error if the packet violates the policy. /// /// This function performs the last check before a packet is @@ -137,6 +152,9 @@ pub struct StandardPolicy { // Packet types. packet_tags: PacketTagCutoffList, + + // Symmetric algorithms. + symmetric_algos: SymmetricAlgorithmCutoffList, } impl Default for StandardPolicy { @@ -182,6 +200,24 @@ a_cutoff_list!(RevocationHashCutoffList, HashAlgorithm, 12, ACCEPT, // 11. SHA224 ]); +a_cutoff_list!(SymmetricAlgorithmCutoffList, SymmetricAlgorithm, 14, + [ + REJECT, // 0. Unencrypted. + ACCEPT, // 1. IDEA. + Some(Timestamp::Y2017), // 2. TripleDES. + ACCEPT, // 3. CAST5. + ACCEPT, // 4. Blowfish. + REJECT, // 5. Reserved. + REJECT, // 6. Reserved. + ACCEPT, // 7. AES128. + ACCEPT, // 8. AES192. + ACCEPT, // 9. AES256. + ACCEPT, // 10. Twofish. + ACCEPT, // 11. Camellia128. + ACCEPT, // 12. Camellia192. + ACCEPT, // 13. Camellia256. + ]); + a_cutoff_list!(PacketTagCutoffList, Tag, 21, [ REJECT, // 0. Reserved. @@ -239,6 +275,7 @@ impl StandardPolicy { time: None, hash_algos_normal: NormalHashCutoffList::Default(), hash_algos_revocation: RevocationHashCutoffList::Default(), + symmetric_algos: SymmetricAlgorithmCutoffList::Default(), packet_tags: PacketTagCutoffList::Default(), } } @@ -373,6 +410,47 @@ impl StandardPolicy { self.hash_algos_revocation.cutoff(h).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); + } + + /// Always considers `s` to be insecure. + pub fn reject_symmetric_algo(&mut self, s: SymmetricAlgorithm) { + self.symmetric_algos.set(s, REJECT); + } + + /// 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 + /// 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. + /// + /// ["MUST implement"]: https://tools.ietf.org/html/rfc4880#section-9.2 + /// [excellent]: https://tests.sequoia-pgp.org/#Symmetric_Encryption_Algorithm_support + /// [NIST deprecated 3DES]: https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA + pub fn reject_symmetric_algo_at<C>(&mut self, s: SymmetricAlgorithm, + cutoff: C) + where C: Into<Option<SystemTime>>, + { + self.symmetric_algos.set( + s, + cutoff.into().and_then(system_time_cutoff_to_timestamp)); + } + + /// Returns the cutoff times for the specified hash algorithm. + pub fn symmetric_algo_cutoff(&self, s: SymmetricAlgorithm) + -> Option<SystemTime> { + self.symmetric_algos.cutoff(s).map(|t| t.into()) + } + /// Always accept packets with the given tag. pub fn accept_packet_tag(&mut self, tag: Tag) { self.packet_tags.set(tag, ACCEPT); @@ -446,6 +524,11 @@ impl Policy for StandardPolicy { let time = self.time.unwrap_or_else(Timestamp::now); self.packet_tags.check(packet.tag(), time) } + + fn symmetric_algorithm(&self, algo: SymmetricAlgorithm) -> Result<()> { + let time = self.time.unwrap_or_else(Timestamp::now); + self.symmetric_algos.check(algo, time) + } } #[cfg(test)] @@ -1307,4 +1390,54 @@ mod test { = e.downcast().unwrap()), } } + + #[test] + fn reject_cipher() { + struct Helper {} + impl VerificationHelper for Helper { + fn get_public_keys(&mut self, _: &[crate::KeyHandle]) + -> Result<Vec<Cert>> { + Ok(Default::default()) + } + + fn check(&mut self, _: MessageStructure) -> Result<()> { + Ok(()) + } + } + + impl DecryptionHelper for Helper { + fn decrypt<D>(&mut self, pkesks: &[PKESK], _: &[SKESK], + algo: Option<SymmetricAlgorithm>, mut decrypt: D) + -> Result<Option<Fingerprint>> + where D: FnMut(SymmetricAlgorithm, &SessionKey) -> Result<()> + { + let p = &P::new(); + let mut pair = Cert::from_bytes( + crate::tests::key("testy-private.pgp"))? + .keys().with_policy(p, None) + .for_transport_encryption().secret().nth(0).unwrap() + .key().clone().into_keypair()?; + pkesks[0].decrypt(&mut pair, algo) + .and_then(|(algo, session_key)| decrypt(algo, &session_key)) + .map(|_| None) + } + } + + let p = &P::new(); + Decryptor::from_bytes( + p, crate::tests::message("encrypted-to-testy.gpg"), + Helper {}, crate::frozen_time()).unwrap(); + + // Reject the AES256. + let p = &mut P::new(); + p.reject_symmetric_algo(SymmetricAlgorithm::AES256); + let r = Decryptor::from_bytes( + p, crate::tests::message("encrypted-to-testy.gpg"), + Helper {}, crate::frozen_time()); + match r { + Ok(_) => panic!(), + Err(e) => assert_match!(Error::PolicyViolation(_, _) + = e.downcast().unwrap()), + } + } } |