anubis-age 1.4.0

Post-quantum secure encryption library with hybrid X25519+ML-KEM-1024 mode (internal dependency for anubis-rage)
Documentation
//! Common helpers for CLI binaries.

use std::io;

use crate::Callbacks;

mod error;
pub use error::ReadError;

pub mod file_io;

mod identities;
pub use identities::read_identities;

mod recipients;
pub use recipients::read_recipients;

/// A guard that helps to ensure that standard input is only used once.
pub struct StdinGuard {
    stdin_used: bool,
}

impl StdinGuard {
    /// Constructs a new `StdinGuard`.
    ///
    /// `input_is_stdin` should be set to `true` if standard input is being used for
    /// plaintext input during encryption, or ciphertext input during decryption.
    pub fn new(input_is_stdin: bool) -> Self {
        Self {
            stdin_used: input_is_stdin,
        }
    }

    fn open(&mut self, filename: String) -> Result<file_io::InputReader, ReadError> {
        let input = file_io::InputReader::new(Some(filename))?;
        if matches!(input, file_io::InputReader::Stdin(_)) {
            if self.stdin_used {
                return Err(ReadError::MultipleStdin);
            }
            self.stdin_used = true;
        }
        Ok(input)
    }
}

/// Implementation of age callbacks that makes requests to the user via the UI.
#[derive(Clone, Copy)]
pub struct UiCallbacks;

impl Callbacks for UiCallbacks {
    fn display_message(&self, message: &str) {
        eprintln!("{}", message);
    }

    fn confirm(&self, message: &str, yes_string: &str, no_string: Option<&str>) -> Option<bool> {
        #[cfg(feature = "console")]
        {
            let term = console::Term::stderr();
            let cancel_str = no_string.unwrap_or("no");
            let initial = format!("{}: ({}/{}) ", message, yes_string, cancel_str);
            loop {
                term.write_str(&initial).ok()?;
                let response = term.read_line().ok()?.to_lowercase();
                if ["y", "yes"].contains(&response.as_str())
                    || response == yes_string.to_lowercase()
                {
                    break Some(true);
                } else if ["n", "no"].contains(&response.as_str())
                    || Some(response.as_str()) == no_string.map(|s| s.to_lowercase()).as_deref()
                {
                    break Some(false);
                }
            }
        }
        #[cfg(not(feature = "console"))]
        {
            let _ = (message, yes_string, no_string);
            None
        }
    }

    fn request_public_string(&self, description: &str) -> Option<String> {
        #[cfg(feature = "console")]
        {
            let term = console::Term::stderr();
            term.write_str(description).ok()?;
            term.read_line().ok().filter(|s| !s.is_empty())
        }
        #[cfg(not(feature = "console"))]
        {
            let _ = description;
            None
        }
    }

    fn request_passphrase(&self, _description: &str) -> Option<anubis_core::secrecy::SecretString> {
        // Passphrase encryption is not supported in ML-KEM-only mode
        None
    }
}