use std::fs::File;
use std::io;
use crate::{x25519, Callbacks, DecryptError, EncryptError};
#[cfg(feature = "cli-common")]
use crate::cli_common::file_io::InputReader;
#[cfg(feature = "plugin")]
use crate::plugin;
#[derive(Clone)]
pub enum IdentityFileEntry {
Native(x25519::Identity),
#[cfg(feature = "plugin")]
#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
Plugin(plugin::Identity),
}
impl IdentityFileEntry {
#[allow(unused_variables)]
pub(crate) fn into_identity(
self,
callbacks: impl Callbacks,
) -> Result<Box<dyn crate::Identity>, DecryptError> {
match self {
IdentityFileEntry::Native(i) => Ok(Box::new(i)),
#[cfg(feature = "plugin")]
IdentityFileEntry::Plugin(i) => Ok(Box::new(crate::plugin::IdentityPluginV1::new(
i.plugin(),
&[i.clone()],
callbacks,
)?)),
}
}
#[allow(unused_variables)]
pub(crate) fn to_recipient(
&self,
callbacks: impl Callbacks,
) -> Result<Box<dyn crate::Recipient + Send>, EncryptError> {
match self {
IdentityFileEntry::Native(i) => Ok(Box::new(i.to_public())),
#[cfg(feature = "plugin")]
IdentityFileEntry::Plugin(i) => Ok(Box::new(crate::plugin::RecipientPluginV1::new(
i.plugin(),
&[],
&[i.clone()],
callbacks,
)?)),
}
}
}
pub struct IdentityFile {
identities: Vec<IdentityFileEntry>,
}
impl IdentityFile {
pub fn from_file(filename: String) -> io::Result<Self> {
File::open(&filename)
.map(io::BufReader::new)
.and_then(|data| IdentityFile::parse_identities(Some(filename), data))
}
pub fn from_buffer<R: io::BufRead>(data: R) -> io::Result<Self> {
Self::parse_identities(None, data)
}
#[cfg(feature = "cli-common")]
pub fn from_input_reader(reader: InputReader) -> io::Result<Self> {
let filename = reader.filename().map(String::from);
Self::parse_identities(filename, io::BufReader::new(reader))
}
fn parse_identities<R: io::BufRead>(filename: Option<String>, data: R) -> io::Result<Self> {
let mut identities = vec![];
for (line_number, line) in data.lines().enumerate() {
let line = line?;
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Ok(identity) = line.parse::<x25519::Identity>() {
identities.push(IdentityFileEntry::Native(identity));
} else if let Some(identity) = {
#[cfg(feature = "plugin")]
{
line.parse::<plugin::Identity>().ok()
}
#[cfg(not(feature = "plugin"))]
None
} {
#[cfg(feature = "plugin")]
{
identities.push(IdentityFileEntry::Plugin(identity));
}
#[cfg(not(feature = "plugin"))]
let _: () = identity;
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
if let Some(filename) = filename {
format!(
"identity file {} contains non-identity data on line {}",
filename,
line_number + 1
)
} else {
format!(
"identity file contains non-identity data on line {}",
line_number + 1
)
},
));
}
}
Ok(IdentityFile { identities })
}
pub fn into_identities(self) -> Vec<IdentityFileEntry> {
self.identities
}
}
#[cfg(test)]
pub(crate) mod tests {
use age_core::secrecy::ExposeSecret;
use std::io::BufReader;
use super::{IdentityFile, IdentityFileEntry};
pub(crate) const TEST_SK: &str =
"AGE-SECRET-KEY-1GQ9778VQXMMJVE8SK7J6VT8UJ4HDQAJUVSFCWCM02D8GEWQ72PVQ2Y5J33";
fn valid_secret_key_encoding(keydata: &str, num_keys: usize) {
let buf = BufReader::new(keydata.as_bytes());
let f = IdentityFile::from_buffer(buf).unwrap();
assert_eq!(f.identities.len(), num_keys);
match &f.identities[0] {
IdentityFileEntry::Native(identity) => {
assert_eq!(identity.to_string().expose_secret(), TEST_SK)
}
#[cfg(feature = "plugin")]
IdentityFileEntry::Plugin(_) => panic!(),
}
}
#[test]
fn secret_key_encoding() {
valid_secret_key_encoding(TEST_SK, 1);
}
#[test]
fn secret_key_lf() {
valid_secret_key_encoding(&format!("{}\n", TEST_SK), 1);
}
#[test]
fn two_secret_keys_lf() {
valid_secret_key_encoding(&format!("{}\n{}", TEST_SK, TEST_SK), 2);
}
#[test]
fn secret_key_with_comment_lf() {
valid_secret_key_encoding(&format!("# Foo bar baz\n{}", TEST_SK), 1);
valid_secret_key_encoding(&format!("{}\n# Foo bar baz", TEST_SK), 1);
}
#[test]
fn secret_key_with_empty_line_lf() {
valid_secret_key_encoding(&format!("\n\n{}", TEST_SK), 1);
}
#[test]
fn secret_key_crlf() {
valid_secret_key_encoding(&format!("{}\r\n", TEST_SK), 1);
}
#[test]
fn two_secret_keys_crlf() {
valid_secret_key_encoding(&format!("{}\r\n{}", TEST_SK, TEST_SK), 2);
}
#[test]
fn secret_key_with_comment_crlf() {
valid_secret_key_encoding(&format!("# Foo bar baz\r\n{}", TEST_SK), 1);
valid_secret_key_encoding(&format!("{}\r\n# Foo bar baz", TEST_SK), 1);
}
#[test]
fn secret_key_with_empty_line_crlf() {
valid_secret_key_encoding(&format!("\r\n\r\n{}", TEST_SK), 1);
}
#[test]
fn incomplete_secret_key_encoding() {
let buf = BufReader::new(&TEST_SK.as_bytes()[..4]);
assert!(IdentityFile::from_buffer(buf).is_err());
}
}