Skip to content

Commit

Permalink
age: Remove Callbacks from decryption APIs
Browse files Browse the repository at this point in the history
Identities are now expected to handle any user callbacks themselves.
age::ssh::Identity now has a with_callbacks method that returns an
implementation of age::Identity.
  • Loading branch information
str4d committed Jul 12, 2020
1 parent e8a7f02 commit ce13b2a
Show file tree
Hide file tree
Showing 10 changed files with 41 additions and 79 deletions.
3 changes: 2 additions & 1 deletion age/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ to 1.0.0 are beta releases.
- `age::Encryptor::wrap_async_output()`
- `age::Decryptor::new_async()`
- `age::decryptor::RecipientsDecryptor::decrypt_async()`
- `age::decryptor::RecipientsDecryptor::decrypt_async_with_callbacks()`
- `age::decryptor::PassphraseDecryptor::decrypt_async()`
- `age::armor::ArmoredReader`, which can be wrapped around an input to handle
a potentially-armored age file.
Expand All @@ -45,6 +44,8 @@ to 1.0.0 are beta releases.
### Removed
- `age::keys::{Identity, IdentityKey}` (replaced by `age::Identity` trait on
individual identities, and `age::IdentityFile` for parsing identities).
- `age::decryptor::RecipientsDecryptor::decrypt_with_callbacks()` (identities
are now expected to handle their own callbacks).

## [0.4.0] - 2020-03-25
### Added
Expand Down
2 changes: 1 addition & 1 deletion age/src/cli_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ where
Ok(crate::ssh::Identity::Unsupported(k)) => {
return Err(unsupported_ssh(filename, k))
}
Ok(identity) => identities.push(Box::new(identity)),
Ok(identity) => identities.push(Box::new(identity.with_callbacks(UiCallbacks))),
Err(_) => {
// Try parsing as multiple single-line age identities.
identities.push(Box::new(
Expand Down
7 changes: 1 addition & 6 deletions age/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{
error::Error,
format::RecipientStanza,
keys::{FileKey, SecretKey},
protocol::Callbacks,
Identity,
};

Expand Down Expand Up @@ -62,11 +61,7 @@ impl IdentityFile {
}

impl Identity for IdentityFile {
fn unwrap_file_key(
&self,
stanza: &RecipientStanza,
_callbacks: &dyn Callbacks,
) -> Option<Result<FileKey, Error>> {
fn unwrap_file_key(&self, stanza: &RecipientStanza) -> Option<Result<FileKey, Error>> {
self.identities
.iter()
.find_map(|identity| identity.unwrap_file_key(stanza))
Expand Down
23 changes: 4 additions & 19 deletions age/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
error::Error,
format::{x25519, HeaderV1, RecipientStanza},
primitives::{stream::PayloadKey, HmacKey},
protocol::{Callbacks, Nonce},
protocol::Nonce,
ssh,
};

Expand Down Expand Up @@ -101,15 +101,10 @@ impl SecretKey {
pub fn to_public(&self) -> RecipientKey {
RecipientKey::X25519((&self.0).into())
}
}

/// Returns:
/// - `Some(Ok(file_key))` on success.
/// - `Some(Err(e))` if a decryption error occurs.
/// - `None` if the [`RecipientStanza`] does not match this key.
pub(crate) fn unwrap_file_key(
&self,
stanza: &RecipientStanza,
) -> Option<Result<FileKey, Error>> {
impl crate::Identity for SecretKey {
fn unwrap_file_key(&self, stanza: &RecipientStanza) -> Option<Result<FileKey, Error>> {
match stanza {
RecipientStanza::X25519(r) => {
// A failure to decrypt is non-fatal (we try to decrypt the recipient
Expand All @@ -122,16 +117,6 @@ impl SecretKey {
}
}

impl crate::Identity for SecretKey {
fn unwrap_file_key(
&self,
stanza: &RecipientStanza,
_callbacks: &dyn Callbacks,
) -> Option<Result<FileKey, Error>> {
self.unwrap_file_key(stanza)
}
}

/// A key that can be used to encrypt a file to a recipient.
#[derive(Clone, Debug)]
pub enum RecipientKey {
Expand Down
1 change: 0 additions & 1 deletion age/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ pub trait Identity {
fn unwrap_file_key(
&self,
stanza: &format::RecipientStanza,
callbacks: &dyn Callbacks,
) -> Option<Result<keys::FileKey, Error>>;
}

Expand Down
8 changes: 0 additions & 8 deletions age/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,6 @@ pub trait Callbacks {
fn request_passphrase(&self, description: &str) -> Option<SecretString>;
}

struct NoCallbacks;

impl Callbacks for NoCallbacks {
fn request_passphrase(&self, _description: &str) -> Option<SecretString> {
None
}
}

/// Handles the various types of age encryption.
enum EncryptorType {
/// Encryption to a list of recipients identified by keys.
Expand Down
37 changes: 4 additions & 33 deletions age/src/protocol/decryptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use secrecy::SecretString;
use std::io::Read;

use super::{Callbacks, NoCallbacks, Nonce};
use super::Nonce;
use crate::{
error::Error,
format::{Header, RecipientStanza},
Expand Down Expand Up @@ -56,36 +56,21 @@ impl<R> RecipientsDecryptor<R> {
fn obtain_payload_key(
&self,
mut identities: impl Iterator<Item = Box<dyn Identity>>,
callbacks: &dyn Callbacks,
) -> Result<PayloadKey, Error> {
self.0
.obtain_payload_key(|r| identities.find_map(|key| key.unwrap_file_key(r, callbacks)))
.obtain_payload_key(|r| identities.find_map(|key| key.unwrap_file_key(r)))
}
}

impl<R: Read> RecipientsDecryptor<R> {
/// Attempts to decrypt the age file.
///
/// The decryptor will have no callbacks registered, so it will be unable to use
/// identities that require e.g. a passphrase to decrypt.
///
/// If successful, returns a reader that will provide the plaintext.
pub fn decrypt(
self,
identities: impl Iterator<Item = Box<dyn Identity>>,
) -> Result<StreamReader<R>, Error> {
self.decrypt_with_callbacks(identities, &NoCallbacks)
}

/// Attempts to decrypt the age file.
///
/// If successful, returns a reader that will provide the plaintext.
pub fn decrypt_with_callbacks(
self,
identities: impl Iterator<Item = Box<dyn Identity>>,
callbacks: &dyn Callbacks,
) -> Result<StreamReader<R>, Error> {
self.obtain_payload_key(identities, callbacks)
self.obtain_payload_key(identities)
.map(|payload_key| Stream::decrypt(payload_key, self.0.input))
}
}
Expand All @@ -94,26 +79,12 @@ impl<R: Read> RecipientsDecryptor<R> {
impl<R: AsyncRead + Unpin> RecipientsDecryptor<R> {
/// Attempts to decrypt the age file.
///
/// The decryptor will have no callbacks registered, so it will be unable to use
/// identities that require e.g. a passphrase to decrypt.
///
/// If successful, returns a reader that will provide the plaintext.
pub fn decrypt_async(
self,
identities: impl Iterator<Item = Box<dyn Identity>>,
) -> Result<StreamReader<R>, Error> {
self.decrypt_async_with_callbacks(identities, &NoCallbacks)
}

/// Attempts to decrypt the age file.
///
/// If successful, returns a reader that will provide the plaintext.
pub fn decrypt_async_with_callbacks(
self,
identities: impl Iterator<Item = Box<dyn Identity>>,
callbacks: &dyn Callbacks,
) -> Result<StreamReader<R>, Error> {
self.obtain_payload_key(identities, callbacks)
self.obtain_payload_key(identities)
.map(|payload_key| Stream::decrypt_async(payload_key, self.0.input))
}
}
Expand Down
31 changes: 25 additions & 6 deletions age/src/ssh/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,18 +178,37 @@ impl Identity {
}
}
}

/// Wraps this identity with the provided callbacks, so that if this is an encrypted
/// identity, it can potentially be decrypted.
pub fn with_callbacks<C: Callbacks>(self, callbacks: C) -> impl crate::Identity {
DecryptableIdentity {
identity: self,
callbacks,
}
}
}

impl crate::Identity for Identity {
fn unwrap_file_key(
&self,
stanza: &RecipientStanza,
callbacks: &dyn Callbacks,
) -> Option<Result<FileKey, Error>> {
fn unwrap_file_key(&self, stanza: &RecipientStanza) -> Option<Result<FileKey, Error>> {
match self {
Identity::Unencrypted(key) => key.unwrap_file_key(stanza),
Identity::Encrypted(_) | Identity::Unsupported(_) => None,
}
}
}

struct DecryptableIdentity<C: Callbacks> {
identity: Identity,
callbacks: C,
}

impl<C: Callbacks> crate::Identity for DecryptableIdentity<C> {
fn unwrap_file_key(&self, stanza: &RecipientStanza) -> Option<Result<FileKey, Error>> {
match &self.identity {
Identity::Unencrypted(key) => key.unwrap_file_key(stanza),
Identity::Encrypted(enc) => {
let passphrase = callbacks.request_passphrase(&format!(
let passphrase = self.callbacks.request_passphrase(&format!(
"Type passphrase for OpenSSH key '{}'",
enc.filename
.as_ref()
Expand Down
4 changes: 2 additions & 2 deletions rage/src/bin/rage-mount/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use age::{
armor::ArmoredReader,
cli_common::{read_identities, read_secret, UiCallbacks},
cli_common::{read_identities, read_secret},
stream::StreamReader,
};
use fuse_mt::FilesystemMT;
Expand Down Expand Up @@ -199,7 +199,7 @@ fn main() -> Result<(), Error> {
)?;

decryptor
.decrypt_with_callbacks(identities.into_iter(), &UiCallbacks)
.decrypt(identities.into_iter())
.map_err(|e| e.into())
.and_then(|stream| mount_stream(stream, types, mountpoint))
}
Expand Down
4 changes: 2 additions & 2 deletions rage/src/bin/rage/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use age::{
armor::{ArmoredReader, ArmoredWriter, Format},
cli_common::{
file_io, get_config_dir, read_identities, read_or_generate_passphrase, read_secret,
Passphrase, UiCallbacks,
Passphrase,
},
};
use gumdrop::{Options, ParsingStyle};
Expand Down Expand Up @@ -354,7 +354,7 @@ fn decrypt(opts: AgeOptions) -> Result<(), error::DecryptError> {
)?;

decryptor
.decrypt_with_callbacks(identities.into_iter(), &UiCallbacks)
.decrypt(identities.into_iter())
.map_err(|e| e.into())
.and_then(|input| write_output(input, output))
}
Expand Down

0 comments on commit ce13b2a

Please sign in to comment.