Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redwood: Relax requirements for decrypting messages #7000

Merged
merged 1 commit into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions redwood/src/decryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ use crate::keys::secret_key_from_cert;
use anyhow::anyhow;
use sequoia_openpgp::crypto::{Password, SessionKey};
use sequoia_openpgp::parse::stream::*;
use sequoia_openpgp::policy::Policy;
use sequoia_openpgp::types::SymmetricAlgorithm;

pub(crate) struct Helper<'a> {
pub(crate) policy: &'a dyn Policy,
pub(crate) secret: &'a sequoia_openpgp::Cert,
pub(crate) passphrase: &'a Password,
}
Expand Down Expand Up @@ -45,7 +43,7 @@ impl<'a> DecryptionHelper for Helper<'a> {
where
D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool,
{
let key = secret_key_from_cert(self.policy, self.secret)?;
let key = secret_key_from_cert(self.secret)?;

for pkesk in pkesks {
// Note: this check won't work for messages encrypted with --throw-keyids,
Expand Down
62 changes: 36 additions & 26 deletions redwood/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,9 @@ use crate::{Error, Result};
use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation;
use sequoia_openpgp::packet::key::{PublicParts, SecretParts, UnspecifiedRole};
use sequoia_openpgp::packet::Key;
use sequoia_openpgp::policy::Policy;
use sequoia_openpgp::policy::{NullPolicy, Policy};
use sequoia_openpgp::Cert;

/// We want to use the same iterators on public and secret keys but it's not
/// really possible to do it in a function because of type differences so we
/// use a macro instead.
macro_rules! filter_keys {
( $keys:expr, $policy: ident ) => {
$keys
.with_policy($policy, None)
.supported()
.alive()
.revoked(false)
.for_storage_encryption()
};
}

/// Get public encryption keys from the specified cert, returning an error if
/// no valid keys are found.
pub(crate) fn keys_from_cert<'a>(
Expand All @@ -27,7 +13,14 @@ pub(crate) fn keys_from_cert<'a>(
) -> Result<Vec<ValidErasedKeyAmalgamation<'a, PublicParts>>> {
// Pull the encryption keys that are compatible with by the standard policy
// (e.g. not SHA-1) supported by Sequoia, and not revoked.
let keys: Vec<_> = filter_keys!(cert.keys(), policy).collect();
let keys: Vec<_> = cert
.keys()
.with_policy(policy, None)
.supported()
.alive()
.revoked(false)
.for_storage_encryption()
.collect();

// Each certificate must have at least one supported encryption key
if keys.is_empty() {
Expand All @@ -37,18 +30,35 @@ pub(crate) fn keys_from_cert<'a>(
}
}

/// Get the secret encryption key, which is the first and only subkey, from the cert.
pub(crate) fn secret_key_from_cert<'a>(
policy: &'a dyn Policy,
cert: &'a Cert,
/// Get the secret encryption key from the cert.
pub(crate) fn secret_key_from_cert(
cert: &Cert,
) -> Result<Key<SecretParts, UnspecifiedRole>> {
// Pull the encryption keys that are compatible with by the standard policy
// (e.g. not SHA-1) supported by Sequoia, and not revoked.
// These filter options should be kept in sync with `Helper::decrypt()`.
let keys: Vec<_> = filter_keys!(cert.keys().secret(), policy).collect();
// Get the secret encryption key for decryption. We don't care about
// whether it is revoked or expired, or even if its not a weak key
// since we're just decrypting.
// We first try to find the key using the StandardPolicy and then
// fall back to a NullPolicy to prevent downgrade attacks.
let key = cert
.keys()
.secret()
.with_policy(crate::STANDARD_POLICY, None)
.for_storage_encryption()
legoktm marked this conversation as resolved.
Show resolved Hide resolved
.next();
if let Some(key) = key {
return Ok(key.key().clone());
}

// No key found, try again with a null policy
let null = NullPolicy::new();
let key = cert
.keys()
.secret()
.with_policy(&null, None)
.for_storage_encryption()
.next();

// Just return the first encryption key
match keys.get(0) {
match key {
Some(key) => Ok(key.key().clone()),
None => Err(Error::NoSupportedKeys(cert.fingerprint().to_string())),
}
Expand Down
11 changes: 5 additions & 6 deletions redwood/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ mod decryption;
mod keys;
mod stream;

const POLICY: &StandardPolicy = &StandardPolicy::new();
const STANDARD_POLICY: &StandardPolicy = &StandardPolicy::new();

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand Down Expand Up @@ -104,7 +104,7 @@ pub fn generate_source_key_pair(
pub fn is_valid_public_key(input: &str) -> Result<String> {
let cert = Cert::from_str(input)?;
// We don't need the keys, just need to check there's at least one and no error
keys::keys_from_cert(POLICY, &cert)?;
keys::keys_from_cert(STANDARD_POLICY, &cert)?;
// And there is no secret key material
if cert.is_tsk() {
return Err(Error::HasSecretKeyMaterial);
Expand All @@ -116,7 +116,7 @@ pub fn is_valid_public_key(input: &str) -> Result<String> {
pub fn is_valid_secret_key(input: &str, passphrase: String) -> Result<String> {
let passphrase: Password = passphrase.into();
let cert = Cert::from_str(input)?;
let secret_key = keys::secret_key_from_cert(POLICY, &cert)?;
let secret_key = keys::secret_key_from_cert(&cert)?;
secret_key.decrypt_secret(&passphrase)?;
Ok(cert.fingerprint().to_string())
}
Expand Down Expand Up @@ -164,7 +164,7 @@ fn encrypt(
}

for cert in certs.iter() {
recipient_keys.extend(keys::keys_from_cert(POLICY, cert)?);
recipient_keys.extend(keys::keys_from_cert(STANDARD_POLICY, cert)?);
}

// In reverse order, we set up a writer that will write an encrypted and
Expand Down Expand Up @@ -207,14 +207,13 @@ pub fn decrypt(
let recipient = Cert::from_str(&secret_key)?;
let passphrase: Password = passphrase.into();
let helper = decryption::Helper {
policy: POLICY,
secret: &recipient,
passphrase: &passphrase,
};

// Now, create a decryptor with a helper using the given Certs.
let mut decryptor = DecryptorBuilder::from_bytes(&ciphertext)?
.with_policy(POLICY, None, helper)?;
.with_policy(STANDARD_POLICY, None, helper)?;

// Decrypt the data.
let mut buffer: Vec<u8> = vec![];
Expand Down
Loading