anubis-age 1.4.0

Post-quantum secure encryption library with hybrid X25519+ML-KEM-1024 mode (internal dependency for anubis-rage)
Documentation
use std::io::{self, BufReader};

use super::ReadError;
use super::{StdinGuard, UiCallbacks};
use crate::identity::{IdentityFile, RecipientsAccumulator};
use crate::{pqc::mlkem, Recipient};

use crate::EncryptError;

/// Parses a recipient from a string.
fn parse_recipient(
    _filename: &str,
    s: String,
    recipients: &mut RecipientsAccumulator,
) -> Result<(), ReadError> {
    if let Ok(pk) = s.parse::<mlkem::Recipient>() {
        recipients.push(Box::new(pk));
        return Ok(());
    }

    Err(ReadError::NonLevel5Recipient(s))
}

/// Reads file contents as a list of recipients
fn read_recipients_list<R: io::BufRead>(
    filename: &str,
    buf: R,
    recipients: &mut RecipientsAccumulator,
) -> Result<(), ReadError> {
    for (line_number, line) in buf.lines().enumerate() {
        let line = line?;

        // Skip empty lines and comments
        if line.is_empty() || line.find('#') == Some(0) {
            continue;
        } else {
            match parse_recipient(filename, line, recipients) {
                Ok(()) => {}
                Err(err @ ReadError::InvalidRecipient(_)) => {
                    return Err(ReadError::InvalidRecipientsFile {
                        filename: filename.to_owned(),
                        line_number: line_number + 1,
                    });
                }
                Err(err) => {
                    return Err(err);
                }
            }
        }
    }

    Ok(())
}

/// Reads recipients from the provided arguments.
///
/// `recipients_file_strings` and `identity_strings` may collectively contain at most one
/// entry of `"-"`, which will be interpreted as reading from standard input. An error
/// will be returned if `stdin_guard` is guarding an existing usage of standard input.
pub fn read_recipients(
    recipient_strings: Vec<String>,
    recipients_file_strings: Vec<String>,
    identity_strings: Vec<String>,
    max_work_factor: Option<u8>,
    stdin_guard: &mut StdinGuard,
) -> Result<Vec<Box<dyn Recipient + Send>>, ReadError> {
    let mut recipients = RecipientsAccumulator::new();

    for arg in recipient_strings {
        parse_recipient("", arg, &mut recipients)?;
    }

    for arg in recipients_file_strings {
        let f = stdin_guard.open(arg.clone()).map_err(|e| match e {
            ReadError::Io(e) if matches!(e.kind(), io::ErrorKind::NotFound) => {
                ReadError::MissingRecipientsFile(arg.clone())
            }
            _ => e,
        })?;
        let buf = BufReader::new(f);
        read_recipients_list(&arg, buf, &mut recipients)?;
    }

    // Convert identities to recipients by reading identity files
    for identity_string in identity_strings {
        let reader = stdin_guard
            .open(identity_string.clone())
            .map_err(|e| match e {
                ReadError::Io(e) if matches!(e.kind(), io::ErrorKind::NotFound) => {
                    ReadError::IdentityNotFound(identity_string.clone())
                }
                other => other,
            })?;

        let identity_file = IdentityFile::from_buffer(BufReader::new(reader))
            .map_err(ReadError::from)?
            .with_callbacks(UiCallbacks);

        // Convert the identity file to recipients
        let file_recipients = identity_file.to_recipients().map_err(|e| {
            if let EncryptError::EncryptedIdentities(decrypt_err) = e {
                ReadError::EncryptedIdentities(decrypt_err)
            } else {
                ReadError::Io(io::Error::new(io::ErrorKind::Other, e.to_string()))
            }
        })?;

        for recipient in file_recipients {
            recipients.push(recipient);
        }
    }

    recipients.build().map_err(|e| match e {
        EncryptError::MissingRecipients => ReadError::MissingRecipients,
        _ => ReadError::Io(io::Error::new(io::ErrorKind::Other, e.to_string())),
    })
}