From 118d9d0018187413235452df6e8c4488fc1b773c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 29 Jul 2024 01:45:58 +0000 Subject: [PATCH] age: Remove `EncryptorType` --- age/i18n/en-US/age.ftl | 4 ++++ age/i18n/es-AR/age.ftl | 2 ++ age/i18n/fr/age.ftl | 2 ++ age/i18n/it/age.ftl | 2 ++ age/i18n/ru/age.ftl | 2 ++ age/i18n/zh-CN/age.ftl | 2 ++ age/i18n/zh-TW/age.ftl | 2 ++ age/src/error.rs | 8 ++++++++ age/src/format.rs | 18 ++++++++++++++---- age/src/protocol.rs | 39 ++++++++++++++------------------------- 10 files changed, 52 insertions(+), 29 deletions(-) diff --git a/age/i18n/en-US/age.ftl b/age/i18n/en-US/age.ftl index 4f6af142..830ed4a0 100644 --- a/age/i18n/en-US/age.ftl +++ b/age/i18n/en-US/age.ftl @@ -13,6 +13,8 @@ -age = age -rage = rage +-scrypt-recipient = scrypt::Recipient + -openssh = OpenSSH -ssh-keygen = ssh-keygen -ssh-rsa = ssh-rsa @@ -57,6 +59,8 @@ err-header-mac-invalid = Header MAC is invalid err-key-decryption = Failed to decrypt an encrypted key +err-mixed-recipient-passphrase = {-scrypt-recipient} can't be used with other recipients. + err-no-matching-keys = No matching keys found err-unknown-format = Unknown {-age} format. diff --git a/age/i18n/es-AR/age.ftl b/age/i18n/es-AR/age.ftl index 657760a3..7e39bd62 100644 --- a/age/i18n/es-AR/age.ftl +++ b/age/i18n/es-AR/age.ftl @@ -13,6 +13,8 @@ -age = age -rage = rage +-scrypt-recipient = scrypt::Recipient + -openssh = OpenSSH -ssh-keygen = ssh-keygen -ssh-rsa = ssh-rsa diff --git a/age/i18n/fr/age.ftl b/age/i18n/fr/age.ftl index bcaf0721..0c3a9ea7 100644 --- a/age/i18n/fr/age.ftl +++ b/age/i18n/fr/age.ftl @@ -13,6 +13,8 @@ -age = age -rage = rage +-scrypt-recipient = scrypt::Recipient + -openssh = OpenSSH -ssh-keygen = ssh-keygen -ssh-rsa = ssh-rsa diff --git a/age/i18n/it/age.ftl b/age/i18n/it/age.ftl index 64a1ff23..1f65f7a6 100644 --- a/age/i18n/it/age.ftl +++ b/age/i18n/it/age.ftl @@ -13,6 +13,8 @@ -age = age -rage = rage +-scrypt-recipient = scrypt::Recipient + -openssh = OpenSSH -ssh-keygen = ssh-keygen -ssh-rsa = ssh-rsa diff --git a/age/i18n/ru/age.ftl b/age/i18n/ru/age.ftl index 410a67a3..d8e7f251 100644 --- a/age/i18n/ru/age.ftl +++ b/age/i18n/ru/age.ftl @@ -13,6 +13,8 @@ -age = age -rage = rage +-scrypt-recipient = scrypt::Recipient + -openssh = OpenSSH -ssh-keygen = ssh-keygen -ssh-rsa = ssh-rsa diff --git a/age/i18n/zh-CN/age.ftl b/age/i18n/zh-CN/age.ftl index f38cf1de..5767ee51 100644 --- a/age/i18n/zh-CN/age.ftl +++ b/age/i18n/zh-CN/age.ftl @@ -13,6 +13,8 @@ -age = age -rage = rage +-scrypt-recipient = scrypt::Recipient + -openssh = OpenSSH -ssh-keygen = ssh-keygen -ssh-rsa = ssh-rsa diff --git a/age/i18n/zh-TW/age.ftl b/age/i18n/zh-TW/age.ftl index 871ba939..81808611 100644 --- a/age/i18n/zh-TW/age.ftl +++ b/age/i18n/zh-TW/age.ftl @@ -13,6 +13,8 @@ -age = age -rage = rage +-scrypt-recipient = scrypt::Recipient + -openssh = OpenSSH -ssh-keygen = ssh-keygen -ssh-rsa = ssh-rsa diff --git a/age/src/error.rs b/age/src/error.rs index 04169984..cf40ab29 100644 --- a/age/src/error.rs +++ b/age/src/error.rs @@ -110,6 +110,10 @@ pub enum EncryptError { /// The plugin's binary name. binary_name: String, }, + /// [`scrypt::Recipient`] was mixed with other recipient types. + /// + /// [`scrypt::Recipient`]: crate::scrypt::Recipient + MixedRecipientAndPassphrase, /// Errors from a plugin. #[cfg(feature = "plugin")] #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))] @@ -131,6 +135,7 @@ impl Clone for EncryptError { Self::MissingPlugin { binary_name } => Self::MissingPlugin { binary_name: binary_name.clone(), }, + Self::MixedRecipientAndPassphrase => Self::MixedRecipientAndPassphrase, #[cfg(feature = "plugin")] Self::Plugin(e) => Self::Plugin(e.clone()), } @@ -147,6 +152,9 @@ impl fmt::Display for EncryptError { wlnfl!(f, "err-missing-plugin", plugin_name = binary_name.as_str())?; wfl!(f, "rec-missing-plugin") } + EncryptError::MixedRecipientAndPassphrase => { + wfl!(f, "err-mixed-recipient-passphrase") + } #[cfg(feature = "plugin")] EncryptError::Plugin(errors) => match &errors[..] { [] => unreachable!(), diff --git a/age/src/format.rs b/age/src/format.rs index 10027bc6..23d86acf 100644 --- a/age/src/format.rs +++ b/age/src/format.rs @@ -1,12 +1,13 @@ //! The age file format. -use age_core::format::Stanza; use std::io::{self, BufRead, Read, Write}; +use age_core::format::{grease_the_joint, Stanza}; + use crate::{ error::DecryptError, primitives::{HmacKey, HmacWriter}, - scrypt, + scrypt, EncryptError, }; #[cfg(feature = "async")] @@ -33,13 +34,22 @@ pub(crate) struct HeaderV1 { } impl HeaderV1 { - pub(crate) fn new(recipients: Vec, mac_key: HmacKey) -> Self { + pub(crate) fn new(recipients: Vec, mac_key: HmacKey) -> Result { let mut header = HeaderV1 { recipients, mac: [0; 32], encoded_bytes: None, }; + if header.no_scrypt() { + // Keep the joint well oiled! + header.recipients.push(grease_the_joint()); + } + + if !header.is_valid() { + return Err(EncryptError::MixedRecipientAndPassphrase); + } + let mut mac = HmacWriter::new(mac_key); cookie_factory::gen(write::header_v1_minus_mac(&header), &mut mac) .expect("can serialize Header into HmacWriter"); @@ -47,7 +57,7 @@ impl HeaderV1 { .mac .copy_from_slice(mac.finalize().into_bytes().as_slice()); - header + Ok(header) } pub(crate) fn verify_mac(&self, mac_key: HmacKey) -> Result<(), hmac::digest::MacError> { diff --git a/age/src/protocol.rs b/age/src/protocol.rs index f25128aa..98b90e77 100644 --- a/age/src/protocol.rs +++ b/age/src/protocol.rs @@ -1,6 +1,6 @@ //! Encryption and decryption routines for age. -use age_core::{format::grease_the_joint, secrecy::SecretString}; +use age_core::secrecy::SecretString; use rand::{rngs::OsRng, RngCore}; use std::io::{self, BufRead, Read, Write}; @@ -45,16 +45,10 @@ impl Nonce { } } -/// Handles the various types of age encryption. -enum EncryptorType { - /// Encryption to a list of recipients identified by keys. - Keys(Vec>), - /// Encryption to a passphrase. - Passphrase(SecretString), -} - /// Encryptor for creating an age file. -pub struct Encryptor(EncryptorType); +pub struct Encryptor { + recipients: Vec>, +} impl Encryptor { /// Constructs an `Encryptor` that will create an age file encrypted to a list of @@ -62,7 +56,7 @@ impl Encryptor { /// /// Returns `None` if no recipients were provided. pub fn with_recipients(recipients: Vec>) -> Option { - (!recipients.is_empty()).then_some(Encryptor(EncryptorType::Keys(recipients))) + (!recipients.is_empty()).then_some(Encryptor { recipients }) } /// Returns an `Encryptor` that will create an age file encrypted with a passphrase. @@ -74,29 +68,24 @@ impl Encryptor { /// /// [`x25519::Identity`]: crate::x25519::Identity pub fn with_user_passphrase(passphrase: SecretString) -> Self { - Encryptor(EncryptorType::Passphrase(passphrase)) + Encryptor { + recipients: vec![Box::new(scrypt::Recipient::new(passphrase))], + } } /// Creates the header for this age file. fn prepare_header(self) -> Result<(Header, Nonce, PayloadKey), EncryptError> { let file_key = new_file_key(); - let recipients = match self.0 { - EncryptorType::Keys(recipients) => { - let mut stanzas = Vec::with_capacity(recipients.len() + 1); - for recipient in recipients { - stanzas.append(&mut recipient.wrap_file_key(&file_key)?); - } - // Keep the joint well oiled! - stanzas.push(grease_the_joint()); - stanzas - } - EncryptorType::Passphrase(passphrase) => { - scrypt::Recipient::new(passphrase).wrap_file_key(&file_key)? + let recipients = { + let mut stanzas = Vec::with_capacity(self.recipients.len() + 1); + for recipient in self.recipients { + stanzas.append(&mut recipient.wrap_file_key(&file_key)?); } + stanzas }; - let header = HeaderV1::new(recipients, mac_key(&file_key)); + let header = HeaderV1::new(recipients, mac_key(&file_key))?; let nonce = Nonce::random(); let payload_key = v1_payload_key(&file_key, &header, &nonce).expect("MAC is correct");