From 9ab26bf360a3bca4a712bce9b46693df6bb18aed Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 30 Aug 2024 10:39:05 -0400 Subject: [PATCH] age: Take recipients by reference in `Encryptor::with_recipients` This aligns it with `Decryptor`, and means that recipients can be used to encrypt multiple files without cloning. Part of str4d/rage#353. --- age/CHANGELOG.md | 14 ++++++ age/benches/parser.rs | 20 +++----- age/benches/throughput.rs | 4 +- age/i18n/en-US/age.ftl | 2 + 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 | 4 ++ age/src/lib.rs | 2 +- age/src/primitives/armor.rs | 6 +-- age/src/protocol.rs | 91 ++++++++++++++++++++----------------- rage/i18n/en-US/rage.ftl | 1 - rage/i18n/es-AR/rage.ftl | 1 - rage/i18n/fr/rage.ftl | 1 - rage/i18n/it/rage.ftl | 1 - rage/i18n/ru/rage.ftl | 1 - rage/i18n/zh-CN/rage.ftl | 1 - rage/i18n/zh-TW/rage.ftl | 1 - rage/src/bin/rage/error.rs | 9 ++-- rage/src/bin/rage/main.rs | 13 +++--- 23 files changed, 106 insertions(+), 78 deletions(-) diff --git a/age/CHANGELOG.md b/age/CHANGELOG.md index 383c480b..f6ed1538 100644 --- a/age/CHANGELOG.md +++ b/age/CHANGELOG.md @@ -22,6 +22,20 @@ to 1.0.0 are beta releases. ### Changed - Migrated to `i18n-embed 0.15`. +- `age::Encryptor::with_recipients` now takes recipients by reference instead of + by value. This aligns it with `age::Decryptor` (which takes identities by + reference), and also means that errors with recipients are reported earlier. + This causes the following changes to the API: + - `Encryptor::with_recipients` takes `impl Iterator` + instead of `Vec>`. + - Verification of recipients and generation of stanzas now happens in + `Encryptor::with_recipients` instead of `Encryptor::wrap_output` and + `Encryptor::wrap_async_output`. + - `Encryptor::with_recipients` returns `Result` instead of + `Option`, and `Encryptor::{wrap_output, wrap_async_output}` return + `io::Result>` instead of `Result, EncryptError>`. + - `age::EncryptError` has a new variant `MissingRecipients`, taking the place + of the `None` that `Encryptor::with_recipients` could previously return. - `age::Decryptor` is now an opaque struct instead of an enum with `Recipients` and `Passphrase` variants. - `age::IdentityFile` now has a `C: Callbacks` generic parameter, which defaults diff --git a/age/benches/parser.rs b/age/benches/parser.rs index e67fb579..cea37c4e 100644 --- a/age/benches/parser.rs +++ b/age/benches/parser.rs @@ -1,4 +1,4 @@ -use age::{x25519, Decryptor, Encryptor, Recipient}; +use age::{x25519, Decryptor, Encryptor}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; #[cfg(unix)] @@ -8,7 +8,7 @@ use std::io::Write; fn bench(c: &mut Criterion) { let recipients: Vec<_> = (0..10) - .map(|_| Box::new(x25519::Identity::generate().to_public())) + .map(|_| x25519::Identity::generate().to_public()) .collect(); let mut group = c.benchmark_group("header"); @@ -16,17 +16,11 @@ fn bench(c: &mut Criterion) { group.throughput(Throughput::Elements(count as u64)); group.bench_function(BenchmarkId::new("parse", count), |b| { let mut encrypted = vec![]; - let mut output = Encryptor::with_recipients( - recipients - .iter() - .take(count) - .cloned() - .map(|r| r as Box) - .collect(), - ) - .unwrap() - .wrap_output(&mut encrypted) - .unwrap(); + let mut output = + Encryptor::with_recipients(recipients.iter().take(count).map(|r| r as _)) + .unwrap() + .wrap_output(&mut encrypted) + .unwrap(); output.write_all(&[]).unwrap(); output.finish().unwrap(); diff --git a/age/benches/throughput.rs b/age/benches/throughput.rs index a259218c..8c5c349d 100644 --- a/age/benches/throughput.rs +++ b/age/benches/throughput.rs @@ -52,7 +52,7 @@ fn bench(c: &mut Criterion_) { group.bench_function(BenchmarkId::new("encrypt", size), |b| { b.iter(|| { - let mut output = Encryptor::with_recipients(vec![Box::new(recipient.clone())]) + let mut output = Encryptor::with_recipients(iter::once(&recipient as _)) .unwrap() .wrap_output(io::sink()) .unwrap(); @@ -62,7 +62,7 @@ fn bench(c: &mut Criterion_) { }); group.bench_function(BenchmarkId::new("decrypt", size), |b| { - let mut output = Encryptor::with_recipients(vec![Box::new(recipient.clone())]) + let mut output = Encryptor::with_recipients(iter::once(&recipient as _)) .unwrap() .wrap_output(&mut ct_buf) .unwrap(); diff --git a/age/i18n/en-US/age.ftl b/age/i18n/en-US/age.ftl index be8e9169..f512f214 100644 --- a/age/i18n/en-US/age.ftl +++ b/age/i18n/en-US/age.ftl @@ -74,6 +74,8 @@ err-invalid-recipient-labels = The first recipient requires one or more invalid err-key-decryption = Failed to decrypt an encrypted key +err-missing-recipients = Missing recipients. + err-mixed-recipient-passphrase = {-scrypt-recipient} can't be used with other recipients. err-no-matching-keys = No matching keys found diff --git a/age/i18n/es-AR/age.ftl b/age/i18n/es-AR/age.ftl index 7e39bd62..08a5406c 100644 --- a/age/i18n/es-AR/age.ftl +++ b/age/i18n/es-AR/age.ftl @@ -57,6 +57,8 @@ err-header-mac-invalid = MAC de encabezado inválido. err-key-decryption = No se pudo desencriptar una clave encriptada. +err-missing-recipients = No se encontraron destinatarios. + err-no-matching-keys = No se encontraron claves coincidentes. err-unknown-format = Formato {-age} desconocido. diff --git a/age/i18n/fr/age.ftl b/age/i18n/fr/age.ftl index 803fef23..e2be5640 100644 --- a/age/i18n/fr/age.ftl +++ b/age/i18n/fr/age.ftl @@ -69,6 +69,8 @@ err-header-mac-invalid = Le MAC de l'en-tête est invalide err-key-decryption = Echec du déchiffrement d'une clef chiffrée +err-missing-recipients = Destinataires manquants. + err-no-matching-keys = Aucune clef correspondante n'a été trouvée err-unknown-format = Format {-age} inconnu. diff --git a/age/i18n/it/age.ftl b/age/i18n/it/age.ftl index 3749a0d4..f8c16ca7 100644 --- a/age/i18n/it/age.ftl +++ b/age/i18n/it/age.ftl @@ -69,6 +69,8 @@ err-header-mac-invalid = Il MAC dell'header è invalido err-key-decryption = La decifrazione di una chiave crittografata è fallita +err-missing-recipients = Destinatari mancanti. + err-no-matching-keys = Nessuna chiave corrispondente trovata err-unknown-format = Formato {-age} sconosciuto. diff --git a/age/i18n/ru/age.ftl b/age/i18n/ru/age.ftl index ede9cb5a..1158caca 100644 --- a/age/i18n/ru/age.ftl +++ b/age/i18n/ru/age.ftl @@ -69,6 +69,8 @@ err-header-mac-invalid = Недействительный MAC заголовка err-key-decryption = Не удалось расшифровать зашифрованный ключ +err-missing-recipients = Отсутствуют получатели. + err-no-matching-keys = Не найдены подходящие ключи err-unknown-format = Неизвестный формат {-age}. diff --git a/age/i18n/zh-CN/age.ftl b/age/i18n/zh-CN/age.ftl index 5767ee51..d4aefd1f 100644 --- a/age/i18n/zh-CN/age.ftl +++ b/age/i18n/zh-CN/age.ftl @@ -57,6 +57,8 @@ err-header-mac-invalid = 标头消息认证码 (MAC) 无效 err-key-decryption = 未能解密加密密钥 +err-missing-recipients = 缺少接收方。 + err-no-matching-keys = 未搜索到匹配的密钥 err-unknown-format = 未知的 {-age} 格式。 diff --git a/age/i18n/zh-TW/age.ftl b/age/i18n/zh-TW/age.ftl index 81808611..3caa4621 100644 --- a/age/i18n/zh-TW/age.ftl +++ b/age/i18n/zh-TW/age.ftl @@ -57,6 +57,8 @@ err-header-mac-invalid = 標頭消息認證碼 (MAC) 無效 err-key-decryption = 未能解密加密密鑰 +err-missing-recipients = 缺少接收方。 + err-no-matching-keys = 未搜索到匹配的密鑰 err-unknown-format = 未知的 {-age} 格式。 diff --git a/age/src/error.rs b/age/src/error.rs index 174fba81..5505d4dd 100644 --- a/age/src/error.rs +++ b/age/src/error.rs @@ -189,6 +189,8 @@ pub enum EncryptError { /// The plugin's binary name. binary_name: String, }, + /// The encryptor was not given any recipients. + MissingRecipients, /// [`scrypt::Recipient`] was mixed with other recipient types. /// /// [`scrypt::Recipient`]: crate::scrypt::Recipient @@ -219,6 +221,7 @@ impl Clone for EncryptError { Self::MissingPlugin { binary_name } => Self::MissingPlugin { binary_name: binary_name.clone(), }, + Self::MissingRecipients => Self::MissingRecipients, Self::MixedRecipientAndPassphrase => Self::MixedRecipientAndPassphrase, #[cfg(feature = "plugin")] Self::Plugin(e) => Self::Plugin(e.clone()), @@ -277,6 +280,7 @@ impl fmt::Display for EncryptError { wlnfl!(f, "err-missing-plugin", plugin_name = binary_name.as_str())?; wfl!(f, "rec-missing-plugin") } + EncryptError::MissingRecipients => wfl!(f, "err-missing-recipients"), EncryptError::MixedRecipientAndPassphrase => { wfl!(f, "err-mixed-recipient-passphrase") } diff --git a/age/src/lib.rs b/age/src/lib.rs index 38486acd..70ad0a99 100644 --- a/age/src/lib.rs +++ b/age/src/lib.rs @@ -42,7 +42,7 @@ //! // Encrypt the plaintext to a ciphertext... //! # fn encrypt(pubkey: age::x25519::Recipient, plaintext: &[u8]) -> Result, age::EncryptError> { //! let encrypted = { -//! let encryptor = age::Encryptor::with_recipients(vec![Box::new(pubkey)]) +//! let encryptor = age::Encryptor::with_recipients(iter::once(&pubkey as _)) //! .expect("we provided a recipient"); //! //! let mut encrypted = vec![]; diff --git a/age/src/primitives/armor.rs b/age/src/primitives/armor.rs index 85033ca7..c1ba9681 100644 --- a/age/src/primitives/armor.rs +++ b/age/src/primitives/armor.rs @@ -291,7 +291,7 @@ enum ArmorIs { /// ``` /// # use std::io::Read; /// use std::io::Write; -/// # use std::iter; +/// use std::iter; /// /// # fn run_main() -> Result<(), ()> { /// # let identity = age::x25519::Identity::generate(); @@ -301,7 +301,7 @@ enum ArmorIs { /// /// # fn encrypt(recipient: age::x25519::Recipient, plaintext: &[u8]) -> Result, age::EncryptError> { /// let encrypted = { -/// let encryptor = age::Encryptor::with_recipients(vec![Box::new(recipient)]) +/// let encryptor = age::Encryptor::with_recipients(iter::once(&recipient as _)) /// .expect("we provided a recipient"); /// /// let mut encrypted = vec![]; @@ -664,7 +664,7 @@ enum StartPos { /// # fn run_main() -> Result<(), ()> { /// # fn encrypt(recipient: age::x25519::Recipient, plaintext: &[u8]) -> Result, age::EncryptError> { /// # let encrypted = { -/// # let encryptor = age::Encryptor::with_recipients(vec![Box::new(recipient)]) +/// # let encryptor = age::Encryptor::with_recipients(iter::once(&recipient as _)) /// # .expect("we provided a recipient"); /// # let mut encrypted = vec![]; /// # let mut writer = encryptor.wrap_output( diff --git a/age/src/protocol.rs b/age/src/protocol.rs index 37c6ca34..05109fb4 100644 --- a/age/src/protocol.rs +++ b/age/src/protocol.rs @@ -2,7 +2,9 @@ use age_core::{format::is_arbitrary_string, secrecy::SecretString}; use rand::{rngs::OsRng, RngCore}; + use std::io::{self, BufRead, Read, Write}; +use std::iter; use crate::{ error::{DecryptError, EncryptError}, @@ -47,18 +49,12 @@ impl Nonce { /// Encryptor for creating an age file. pub struct Encryptor { - recipients: Vec>, + header: Header, + nonce: Nonce, + payload_key: PayloadKey, } impl Encryptor { - /// Constructs an `Encryptor` that will create an age file encrypted to a list of - /// recipients. - /// - /// Returns `None` if no recipients were provided. - pub fn with_recipients(recipients: Vec>) -> Option { - (!recipients.is_empty()).then_some(Encryptor { recipients }) - } - /// Returns an `Encryptor` that will create an age file encrypted with a passphrase. /// Anyone with the passphrase can decrypt the file. /// @@ -68,20 +64,24 @@ impl Encryptor { /// /// [`x25519::Identity`]: crate::x25519::Identity pub fn with_user_passphrase(passphrase: SecretString) -> Self { - Encryptor { - recipients: vec![Box::new(scrypt::Recipient::new(passphrase))], - } + Self::with_recipients(iter::once(&scrypt::Recipient::new(passphrase) as _)) + .expect("no errors can occur with this recipient set") } - /// Creates the header for this age file. - fn prepare_header(self) -> Result<(Header, Nonce, PayloadKey), EncryptError> { + /// Constructs an `Encryptor` that will create an age file encrypted to a list of + /// recipients. + pub fn with_recipients<'a>( + recipients: impl Iterator, + ) -> Result { let file_key = new_file_key(); let recipients = { let mut control = None; - let mut stanzas = Vec::with_capacity(self.recipients.len() + 1); - for recipient in self.recipients { + let mut stanzas = vec![]; + let mut have_recipients = false; + for recipient in recipients { + have_recipients = true; let (mut r_stanzas, r_labels) = recipient.wrap_file_key(&file_key)?; if let Some(l_labels) = control.take() { @@ -107,6 +107,9 @@ impl Encryptor { stanzas.append(&mut r_stanzas); } + if !have_recipients { + return Err(EncryptError::MissingRecipients); + } stanzas }; @@ -114,7 +117,11 @@ impl Encryptor { let nonce = Nonce::random(); let payload_key = v1_payload_key(&file_key, &header, &nonce).expect("MAC is correct"); - Ok((Header::V1(header), nonce, payload_key)) + Ok(Self { + header: Header::V1(header), + nonce, + payload_key, + }) } /// Creates a wrapper around a writer that will encrypt its input. @@ -124,8 +131,12 @@ impl Encryptor { /// You **MUST** call [`StreamWriter::finish`] when you are done writing, in order to /// finish the encryption process. Failing to call [`StreamWriter::finish`] will /// result in a truncated file that will fail to decrypt. - pub fn wrap_output(self, mut output: W) -> Result, EncryptError> { - let (header, nonce, payload_key) = self.prepare_header()?; + pub fn wrap_output(self, mut output: W) -> io::Result> { + let Self { + header, + nonce, + payload_key, + } = self; header.write(&mut output)?; output.write_all(nonce.as_ref())?; Ok(Stream::encrypt(payload_key, output)) @@ -143,8 +154,12 @@ impl Encryptor { pub async fn wrap_async_output( self, mut output: W, - ) -> Result, EncryptError> { - let (header, nonce, payload_key) = self.prepare_header()?; + ) -> io::Result> { + let Self { + header, + nonce, + payload_key, + } = self; header.write_async(&mut output).await?; output.write_all(nonce.as_ref()).await?; Ok(Stream::encrypt_async(payload_key, output)) @@ -339,7 +354,7 @@ mod tests { use futures_test::task::noop_context; fn recipient_round_trip<'a>( - recipients: Vec>, + recipients: impl Iterator, identities: impl Iterator, ) { let test_msg = b"This is a test message. For testing."; @@ -362,7 +377,7 @@ mod tests { #[cfg(feature = "async")] fn recipient_async_round_trip<'a>( - recipients: Vec>, + recipients: impl Iterator, identities: impl Iterator, ) { let test_msg = b"This is a test message. For testing."; @@ -441,7 +456,7 @@ mod tests { let f = IdentityFile::from_buffer(buf).unwrap(); let pk: x25519::Recipient = crate::x25519::tests::TEST_PK.parse().unwrap(); recipient_round_trip( - vec![Box::new(pk)], + iter::once(&pk as _), f.into_identities().unwrap().iter().map(|i| i.as_ref()), ); } @@ -453,7 +468,7 @@ mod tests { let f = IdentityFile::from_buffer(buf).unwrap(); let pk: x25519::Recipient = crate::x25519::tests::TEST_PK.parse().unwrap(); recipient_async_round_trip( - vec![Box::new(pk)], + iter::once(&pk as _), f.into_identities().unwrap().iter().map(|i| i.as_ref()), ); } @@ -467,7 +482,7 @@ mod tests { recipient.set_work_factor(2); let mut encrypted = vec![]; - let e = Encryptor::with_recipients(vec![Box::new(recipient)]).unwrap(); + let e = Encryptor::with_recipients(iter::once(&recipient as _)).unwrap(); { let mut w = e.wrap_output(&mut encrypted).unwrap(); w.write_all(test_msg).unwrap(); @@ -495,7 +510,7 @@ mod tests { let pk: crate::ssh::Recipient = crate::ssh::recipient::tests::TEST_SSH_RSA_PK .parse() .unwrap(); - recipient_round_trip(vec![Box::new(pk)], iter::once(&sk as &dyn Identity)); + recipient_round_trip(iter::once(&pk as _), iter::once(&sk as &dyn Identity)); } #[cfg(all(feature = "ssh", feature = "async"))] @@ -506,7 +521,7 @@ mod tests { let pk: crate::ssh::Recipient = crate::ssh::recipient::tests::TEST_SSH_RSA_PK .parse() .unwrap(); - recipient_async_round_trip(vec![Box::new(pk)], iter::once(&sk as &dyn Identity)); + recipient_async_round_trip(iter::once(&pk as _), iter::once(&sk as &dyn Identity)); } #[cfg(feature = "ssh")] @@ -517,7 +532,7 @@ mod tests { let pk: crate::ssh::Recipient = crate::ssh::recipient::tests::TEST_SSH_ED25519_PK .parse() .unwrap(); - recipient_round_trip(vec![Box::new(pk)], iter::once(&sk as &dyn Identity)); + recipient_round_trip(iter::once(&pk as _), iter::once(&sk as &dyn Identity)); } #[cfg(all(feature = "ssh", feature = "async"))] @@ -528,7 +543,7 @@ mod tests { let pk: crate::ssh::Recipient = crate::ssh::recipient::tests::TEST_SSH_ED25519_PK .parse() .unwrap(); - recipient_async_round_trip(vec![Box::new(pk)], iter::once(&sk as &dyn Identity)); + recipient_async_round_trip(iter::once(&pk as _), iter::once(&sk as &dyn Identity)); } #[test] @@ -536,12 +551,10 @@ mod tests { let pk: x25519::Recipient = crate::x25519::tests::TEST_PK.parse().unwrap(); let passphrase = crate::scrypt::Recipient::new(SecretString::new("passphrase".to_string())); - let recipients = vec![Box::new(pk) as _, Box::new(passphrase) as _]; + let recipients = [&pk as &dyn Recipient, &passphrase as _]; - let mut encrypted = vec![]; - let e = Encryptor::with_recipients(recipients).unwrap(); assert!(matches!( - e.wrap_output(&mut encrypted), + Encryptor::with_recipients(recipients.into_iter()), Err(EncryptError::MixedRecipientAndPassphrase), )); } @@ -563,16 +576,12 @@ mod tests { #[test] fn incompatible_recipients() { let pk: x25519::Recipient = crate::x25519::tests::TEST_PK.parse().unwrap(); + let incompatible = IncompatibleRecipient(pk.clone()); - let recipients = vec![ - Box::new(pk.clone()) as _, - Box::new(IncompatibleRecipient(pk)) as _, - ]; + let recipients = [&pk as &dyn Recipient, &incompatible as _]; - let mut encrypted = vec![]; - let e = Encryptor::with_recipients(recipients).unwrap(); assert!(matches!( - e.wrap_output(&mut encrypted), + Encryptor::with_recipients(recipients.into_iter()), Err(EncryptError::IncompatibleRecipients { .. }), )); } diff --git a/rage/i18n/en-US/rage.ftl b/rage/i18n/en-US/rage.ftl index a27f7970..ccc83682 100644 --- a/rage/i18n/en-US/rage.ftl +++ b/rage/i18n/en-US/rage.ftl @@ -153,7 +153,6 @@ rec-enc-broken-stdout = Are you piping to a program that isn't reading from stdi err-enc-broken-file = Could not write to file: {$err} -err-enc-missing-recipients = Missing recipients. rec-enc-missing-recipients = Did you forget to specify {-flag-recipient}? err-enc-mixed-identity-passphrase = {-flag-identity} can't be used with {-flag-passphrase}. diff --git a/rage/i18n/es-AR/rage.ftl b/rage/i18n/es-AR/rage.ftl index bdd27f72..712dd0c8 100644 --- a/rage/i18n/es-AR/rage.ftl +++ b/rage/i18n/es-AR/rage.ftl @@ -120,7 +120,6 @@ rec-enc-broken-stdout = Estás enviando por pipe a un programa que no está leye err-enc-broken-file = No se pudo escribir al archivo: {$err} -err-enc-missing-recipients = No se encontraron destinatarios. rec-enc-missing-recipients = ¿Te olvidaste de especificar {-flag-recipient}? err-enc-mixed-identity-passphrase = {-flag-identity} no puede ser usado con {-flag-passphrase}. diff --git a/rage/i18n/fr/rage.ftl b/rage/i18n/fr/rage.ftl index 802301af..dac6dedb 100644 --- a/rage/i18n/fr/rage.ftl +++ b/rage/i18n/fr/rage.ftl @@ -158,7 +158,6 @@ rec-enc-broken-stdout = Etes-vous en train de piper vers programme qui ne lit pa err-enc-broken-file = N'a pas pu écrire dans le fichier: {$err} -err-enc-missing-recipients = Destinataires manquants. rec-enc-missing-recipients = Avez-vous oublié de spécifier {-flag-recipient} ? err-enc-mixed-identity-passphrase = {-flag-identity} {-cantuse} {-flag-passphrase}. diff --git a/rage/i18n/it/rage.ftl b/rage/i18n/it/rage.ftl index a1442b8d..d88e1542 100644 --- a/rage/i18n/it/rage.ftl +++ b/rage/i18n/it/rage.ftl @@ -152,7 +152,6 @@ rec-enc-broken-stdout = Stai usando una pipe verso un programma che non sta legg err-enc-broken-file = Impossibile scrivere sul file: {$err} -err-enc-missing-recipients = Destinatari mancanti. rec-enc-missing-recipients = Hai dimenticato di specificare {-flag-recipient}? err-enc-mixed-identity-passphrase = {-flag-identity} non può essere usato assieme a {-flag-passphrase}. diff --git a/rage/i18n/ru/rage.ftl b/rage/i18n/ru/rage.ftl index 0d46dd33..89b3153e 100644 --- a/rage/i18n/ru/rage.ftl +++ b/rage/i18n/ru/rage.ftl @@ -154,7 +154,6 @@ rec-enc-broken-stdout = Вы передаете данные в программ err-enc-broken-file = Не удалось записать в файл: {$err} -err-enc-missing-recipients = Отсутствуют получатели. rec-enc-missing-recipients = Вы забыли указать {-flag-recipient}? err-enc-mixed-identity-passphrase = {-flag-identity} не может использоваться с {-flag-passphrase}. diff --git a/rage/i18n/zh-CN/rage.ftl b/rage/i18n/zh-CN/rage.ftl index c65550d9..abd62ad4 100644 --- a/rage/i18n/zh-CN/rage.ftl +++ b/rage/i18n/zh-CN/rage.ftl @@ -118,7 +118,6 @@ rec-enc-broken-stdout = 您是否输出至非从 stdin 读取数据的程序? err-enc-broken-file = 未能写入文件: {$err} -err-enc-missing-recipients = 缺少接收方。 rec-enc-missing-recipients = 您是否忘记指定 {-flag-recipient} 标记? err-enc-mixed-identity-passphrase = {-flag-identity} 和 {-flag-passphrase} 标记不可联用。 diff --git a/rage/i18n/zh-TW/rage.ftl b/rage/i18n/zh-TW/rage.ftl index 7ca24f40..df21098a 100644 --- a/rage/i18n/zh-TW/rage.ftl +++ b/rage/i18n/zh-TW/rage.ftl @@ -118,7 +118,6 @@ rec-enc-broken-stdout = 您是否輸出至非從 stdin 讀取數據的程序? err-enc-broken-file = 未能寫入文件: {$err} -err-enc-missing-recipients = 缺少接收方。 rec-enc-missing-recipients = 您是否忘記指定 {-flag-recipient} 標記? err-enc-mixed-identity-passphrase = {-flag-identity} 和 {-flag-passphrase} 標記不可聯用。 diff --git a/rage/src/bin/rage/error.rs b/rage/src/bin/rage/error.rs index 596e52b3..7ac82e97 100644 --- a/rage/src/bin/rage/error.rs +++ b/rage/src/bin/rage/error.rs @@ -29,7 +29,6 @@ pub(crate) enum EncryptError { }, IdentityRead(age::cli_common::ReadError), Io(io::Error), - MissingRecipients, MixedIdentityAndPassphrase, MixedRecipientAndPassphrase, MixedRecipientsFileAndPassphrase, @@ -63,6 +62,10 @@ impl From for EncryptError { impl fmt::Display for EncryptError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + EncryptError::Age(e @ age::EncryptError::MissingRecipients) => { + writeln!(f, "{}", e)?; + wfl!(f, "rec-enc-missing-recipients") + } EncryptError::Age(e) => write!(f, "{}", e), EncryptError::BrokenPipe { is_stdout, source } => { if *is_stdout { @@ -74,10 +77,6 @@ impl fmt::Display for EncryptError { } EncryptError::IdentityRead(e) => write!(f, "{}", e), EncryptError::Io(e) => write!(f, "{}", e), - EncryptError::MissingRecipients => { - wlnfl!(f, "err-enc-missing-recipients")?; - wfl!(f, "rec-enc-missing-recipients") - } EncryptError::MixedIdentityAndPassphrase => { wfl!(f, "err-enc-mixed-identity-passphrase") } diff --git a/rage/src/bin/rage/main.rs b/rage/src/bin/rage/main.rs index 545e2513..8644ab4e 100644 --- a/rage/src/bin/rage/main.rs +++ b/rage/src/bin/rage/main.rs @@ -172,19 +172,20 @@ fn encrypt(opts: AgeOptions) -> Result<(), error::EncryptError> { } else { if opts.recipient.is_empty() && opts.recipients_file.is_empty() && opts.identity.is_empty() { - return Err(error::EncryptError::MissingRecipients); + return Err(error::EncryptError::Age( + age::EncryptError::MissingRecipients, + )); } - match age::Encryptor::with_recipients(read_recipients( + let recipients = read_recipients( opts.recipient, opts.recipients_file, opts.identity, opts.max_work_factor, &mut stdin_guard, - )?) { - Some(encryptor) => encryptor, - None => return Err(error::EncryptError::MissingRecipients), - } + )?; + + age::Encryptor::with_recipients(recipients.iter().map(|r| r.as_ref() as _))? }; let mut output = encryptor.wrap_output(ArmoredWriter::wrap_output(output, format)?)?;