Skip to content
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
16 changes: 16 additions & 0 deletions age/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ to 1.0.0 are beta releases.
## [Unreleased]
### Added
- `age::encrypted::EncryptedIdentity`
- `age::plugin::ResolveError`

### Changed
- MSRV is now 1.70.0.
Expand All @@ -20,6 +21,21 @@ to 1.0.0 are beta releases.
`Result<Vec<Box<dyn crate::Identity>>, DecryptError>`. This re-enables
cross-thread uses of `IdentityFile`, which were unintentionally disabled in
0.11.0.
- `age::plugin`:
- The following methods now returns `Result<Self, ResolveError>`:
- `Identity::default_for_plugin`
- `RecipientPluginV1::new`
- `IdentityPluginV1::new`
- All existing error enums nameable in the public API are now non-exhaustive:
- `age::{EncryptError, DecryptError}`
- `age::IdentityFileConvertError`
- `age::armor::ArmoredReadError`
- `age::cli_common::ReadError`
- `age::ssh::ParseRecipientKeyError`
- Removed the following error enum variants:
- `age::DecryptError::MissingPlugin`
- `age::EncryptError::MissingPlugin`
- `age::cli_common::ReadError::MissingPlugin`

## [0.11.2] - 2025-12-07
### Fixed
Expand Down
2 changes: 2 additions & 0 deletions age/i18n/en-US/age.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ err-no-matching-keys = No matching keys found
err-unknown-format = Unknown {-age} format.
rec-unknown-format = Have you tried upgrading to the latest version?

err-invalid-plugin-name = Invalid plugin name '{$name}'.

err-missing-plugin = Could not find '{$plugin_name}' on the PATH.
rec-missing-plugin = Have you installed the plugin?

Expand Down
22 changes: 7 additions & 15 deletions age/src/cli_common/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ use std::io;

use crate::{wfl, DecryptError};

#[cfg(feature = "plugin")]
use crate::wlnfl;

/// Errors that can occur while reading recipients or identities.
#[derive(Debug)]
#[non_exhaustive]
pub enum ReadError {
/// An error occured while decrypting passphrase-encrypted identities.
EncryptedIdentities(DecryptError),
Expand All @@ -26,17 +24,14 @@ pub enum ReadError {
},
/// An I/O error occurred while reading.
Io(io::Error),
/// A required plugin could not be found.
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
MissingPlugin {
/// The plugin's binary name.
binary_name: String,
},
/// The given recipients file could not be found.
MissingRecipientsFile(String),
/// Standard input was used by multiple files.
MultipleStdin,
/// Errors from resolving a plugin.
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
PluginResolve(crate::plugin::ResolveError),
/// A recipient is an `ssh-rsa` public key with a modulus larger than we support.
#[cfg(feature = "ssh")]
#[cfg_attr(docsrs, doc(cfg(feature = "ssh")))]
Expand Down Expand Up @@ -89,17 +84,14 @@ impl fmt::Display for ReadError {
line_number = line_number,
),
ReadError::Io(e) => write!(f, "{}", e),
#[cfg(feature = "plugin")]
ReadError::MissingPlugin { binary_name } => {
wlnfl!(f, "err-missing-plugin", plugin_name = binary_name.as_str())?;
wfl!(f, "rec-missing-plugin")
}
ReadError::MissingRecipientsFile(filename) => wfl!(
f,
"err-read-missing-recipients-file",
filename = filename.as_str(),
),
ReadError::MultipleStdin => wfl!(f, "err-read-multiple-stdin"),
#[cfg(feature = "plugin")]
ReadError::PluginResolve(e) => write!(f, "{}", e),
#[cfg(feature = "ssh")]
ReadError::RsaModulusTooLarge => {
wfl!(f, "err-read-rsa-modulus-too-large", max_size = 4096)
Expand Down
8 changes: 3 additions & 5 deletions age/src/cli_common/identities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,13 @@ pub fn read_identities(
#[cfg(feature = "plugin")]
let new_identities = new_identities.map_err(|e| match e {
#[cfg(feature = "plugin")]
crate::DecryptError::MissingPlugin { binary_name } => {
ReadError::MissingPlugin { binary_name }
}
// DecryptError::MissingPlugin is the only possible error kind returned by
crate::DecryptError::PluginResolve(e) => ReadError::PluginResolve(e),
// DecryptError::PluginResolve is the only possible error kind returned by
// IdentityFileEntry::into_identity.
_ => unreachable!(),
})?;

// IdentityFileEntry::into_identity will never return a MissingPlugin error
// IdentityFileEntry::into_identity will never return a PluginResolve error
// when plugin feature is not enabled.
#[cfg(not(feature = "plugin"))]
let new_identities = new_identities.unwrap();
Expand Down
4 changes: 2 additions & 2 deletions age/src/cli_common/recipients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ pub fn read_recipients(
// Only one error can occur here.
#[cfg(feature = "plugin")]
{
if let EncryptError::MissingPlugin { binary_name } = _e {
ReadError::MissingPlugin { binary_name }
if let EncryptError::PluginResolve(e) = _e {
ReadError::PluginResolve(e)
} else {
unreachable!()
}
Expand Down
51 changes: 19 additions & 32 deletions age/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use age_core::format::Stanza;

/// Errors returned when converting an identity file to a recipients file.
#[derive(Debug)]
#[non_exhaustive]
pub enum IdentityFileConvertError {
/// An I/O error occurred while writing out a recipient corresponding to an identity
/// in this file.
Expand Down Expand Up @@ -165,6 +166,7 @@ impl fmt::Display for PluginError {

/// The various errors that can be returned during the encryption process.
#[derive(Debug)]
#[non_exhaustive]
pub enum EncryptError {
/// An error occured while decrypting passphrase-encrypted identities.
EncryptedIdentities(DecryptError),
Expand All @@ -182,13 +184,6 @@ pub enum EncryptError {
InvalidRecipientLabels(HashSet<String>),
/// An I/O error occurred during encryption.
Io(io::Error),
/// A required plugin could not be found.
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
MissingPlugin {
/// The plugin's binary name.
binary_name: String,
},
/// The encryptor was not given any recipients.
MissingRecipients,
/// [`scrypt::Recipient`] was mixed with other recipient types.
Expand All @@ -199,6 +194,10 @@ pub enum EncryptError {
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
Plugin(Vec<PluginError>),
/// Errors from resolving a plugin.
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
PluginResolve(crate::plugin::ResolveError),
}

impl From<io::Error> for EncryptError {
Expand All @@ -217,14 +216,12 @@ impl Clone for EncryptError {
},
Self::InvalidRecipientLabels(labels) => Self::InvalidRecipientLabels(labels.clone()),
Self::Io(e) => Self::Io(io::Error::new(e.kind(), e.to_string())),
#[cfg(feature = "plugin")]
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()),
#[cfg(feature = "plugin")]
Self::PluginResolve(e) => Self::PluginResolve(e.clone()),
}
}
}
Expand Down Expand Up @@ -275,11 +272,6 @@ impl fmt::Display for EncryptError {
labels = print_labels(labels),
),
EncryptError::Io(e) => e.fmt(f),
#[cfg(feature = "plugin")]
EncryptError::MissingPlugin { binary_name } => {
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")
Expand All @@ -296,6 +288,8 @@ impl fmt::Display for EncryptError {
Ok(())
}
},
#[cfg(feature = "plugin")]
EncryptError::PluginResolve(e) => e.fmt(f),
}
}
}
Expand All @@ -312,6 +306,7 @@ impl std::error::Error for EncryptError {

/// The various errors that can be returned during the decryption process.
#[derive(Debug)]
#[non_exhaustive]
pub enum DecryptError {
/// The age file failed to decrypt.
DecryptionFailed,
Expand All @@ -330,19 +325,16 @@ pub enum DecryptError {
Io(io::Error),
/// Failed to decrypt an encrypted key.
KeyDecryptionFailed,
/// A required plugin could not be found.
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
MissingPlugin {
/// The plugin's binary name.
binary_name: String,
},
/// None of the provided keys could be used to decrypt the age file.
NoMatchingKeys,
/// Errors from a plugin.
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
Plugin(Vec<PluginError>),
/// Errors from resolving a plugin.
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
PluginResolve(crate::plugin::ResolveError),
/// An unknown age format, probably from a newer version.
UnknownFormat,
}
Expand All @@ -359,13 +351,11 @@ impl Clone for DecryptError {
Self::InvalidMac => Self::InvalidMac,
Self::Io(e) => Self::Io(io::Error::new(e.kind(), e.to_string())),
Self::KeyDecryptionFailed => Self::KeyDecryptionFailed,
#[cfg(feature = "plugin")]
Self::MissingPlugin { binary_name } => Self::MissingPlugin {
binary_name: binary_name.clone(),
},
Self::NoMatchingKeys => Self::NoMatchingKeys,
#[cfg(feature = "plugin")]
Self::Plugin(e) => Self::Plugin(e.clone()),
#[cfg(feature = "plugin")]
Self::PluginResolve(e) => Self::PluginResolve(e.clone()),
Self::UnknownFormat => Self::UnknownFormat,
}
}
Expand All @@ -387,11 +377,6 @@ impl fmt::Display for DecryptError {
DecryptError::InvalidMac => wfl!(f, "err-header-mac-invalid"),
DecryptError::Io(e) => e.fmt(f),
DecryptError::KeyDecryptionFailed => wfl!(f, "err-key-decryption"),
#[cfg(feature = "plugin")]
DecryptError::MissingPlugin { binary_name } => {
wlnfl!(f, "err-missing-plugin", plugin_name = binary_name.as_str())?;
wfl!(f, "rec-missing-plugin")
}
DecryptError::NoMatchingKeys => wfl!(f, "err-no-matching-keys"),
#[cfg(feature = "plugin")]
DecryptError::Plugin(errors) => match &errors[..] {
Expand All @@ -405,6 +390,8 @@ impl fmt::Display for DecryptError {
Ok(())
}
},
#[cfg(feature = "plugin")]
DecryptError::PluginResolve(e) => e.fmt(f),
DecryptError::UnknownFormat => {
wlnfl!(f, "err-unknown-format")?;
wfl!(f, "rec-unknown-format")
Expand Down
10 changes: 6 additions & 4 deletions age/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl IdentityFileEntry {
#[cfg(feature = "plugin")]
IdentityFileEntry::Plugin(i) => Ok(Box::new(
crate::plugin::Plugin::new(i.plugin())
.map_err(|binary_name| DecryptError::MissingPlugin { binary_name })
.map_err(DecryptError::PluginResolve)
.map(|plugin| {
crate::plugin::IdentityPluginV1::from_parts(plugin, vec![i], callbacks)
})?,
Expand Down Expand Up @@ -279,13 +279,15 @@ impl RecipientsAccumulator {

// Find the required plugins.
for plugin_name in plugin_names {
self.recipients
.push(Box::new(plugin::RecipientPluginV1::new(
self.recipients.push(Box::new(
plugin::RecipientPluginV1::new(
plugin_name,
&self.plugin_recipients,
&self.plugin_identities,
callbacks.clone(),
)?))
)
.map_err(EncryptError::PluginResolve)?,
))
}
}

Expand Down
Loading
Loading