commit
0c97a5fcc8
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
*~
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "rae"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Avril <flanchan@cumallover.me>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
color-eyre = {version = "0.5.3", default-features =false}
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
cryptohelpers = {version = "1.1.2", features=["full", "async", "serialise"]}
|
||||||
|
cfg-if = "0.1.10"
|
||||||
|
tokio = {version = "0.2", features=["full"]}
|
||||||
|
serde = {version ="1.0.116", features=["derive"]}
|
||||||
|
toml = "0.5.6"
|
||||||
|
tracing = "0.1.19"
|
||||||
|
tracing-subscriber = "0.2.12"
|
||||||
|
tracing-futures = "0.2.4"
|
||||||
|
tracing-error = "0.1.2"
|
||||||
|
smallmap = "1.1.2"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
rustc_version = "0.2"
|
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
extern crate rustc_version;
|
||||||
|
use rustc_version::{version, version_meta, Channel};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Assert we haven't travelled back in time
|
||||||
|
assert!(version().unwrap().major >= 1);
|
||||||
|
|
||||||
|
// Set cfg flags depending on release channel
|
||||||
|
match version_meta().unwrap().channel {
|
||||||
|
Channel::Stable => {
|
||||||
|
println!("cargo:rustc-cfg=stable");
|
||||||
|
}
|
||||||
|
Channel::Beta => {
|
||||||
|
println!("cargo:rustc-cfg=beta");
|
||||||
|
}
|
||||||
|
Channel::Nightly => {
|
||||||
|
println!("cargo:rustc-cfg=nightly");
|
||||||
|
}
|
||||||
|
Channel::Dev => {
|
||||||
|
println!("cargo:rustc-cfg=dev");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
//! Argument related errors
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
error,
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ParseErrorKind
|
||||||
|
{
|
||||||
|
ExpectedArg(String),
|
||||||
|
UnknownOption(char),
|
||||||
|
UnknownOptionFor(char, char),
|
||||||
|
CannotCombine(char, char),
|
||||||
|
BadTail(char, String),
|
||||||
|
NotUnique(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(String, ParseErrorKind)> for Error
|
||||||
|
{
|
||||||
|
#[inline] fn from((froms, fromk): (String, ParseErrorKind)) -> Self
|
||||||
|
{
|
||||||
|
Self::Parse(froms, fromk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Error {
|
||||||
|
Parse(String, ParseErrorKind),
|
||||||
|
|
||||||
|
Unknown,
|
||||||
|
AtomInternal,
|
||||||
|
}
|
||||||
|
impl error::Error for Error{}
|
||||||
|
impl fmt::Display for Error
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
_ => write!(f, "unknown error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error
|
||||||
|
{
|
||||||
|
#[inline] fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self
|
||||||
|
{
|
||||||
|
Self::AtomInternal
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
//! Argument stuffs
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
pub fn program_name() -> &'static str
|
||||||
|
{
|
||||||
|
lazy_static!{
|
||||||
|
static ref NAME: String = std::env::args().next().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
&NAME[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usage() -> !
|
||||||
|
{
|
||||||
|
println!(r#"signing encryptor version {pkg_version} (format version {real_version})
|
||||||
|
by {authour} with <3
|
||||||
|
licensed with GPL v3 or later
|
||||||
|
|
||||||
|
Usage: {program} [OPTIONS...] [-e|-d|-a] [-] [<files...>]
|
||||||
|
Usage: {program} --generate RSA [--input <old key>] [--password[=<password>]] [--format BIN|TEXT|PEM] <output> [<public part output>]
|
||||||
|
Usage: {program} --generate AES [--input <old key>] [--password[=<password>]] [--format BIN|TEXT] <output>
|
||||||
|
Usage: {program} --stat <key files...>
|
||||||
|
Usage: {program} --help
|
||||||
|
|
||||||
|
- Stop reading argument flags:
|
||||||
|
-e Encryption mode
|
||||||
|
-d Decryption mode
|
||||||
|
-a Autodetect mode (default)
|
||||||
|
OPTIONS:
|
||||||
|
-t <inp> <out> Process <input> to <output> (autodetect mode)
|
||||||
|
-te <inp> <out> Process <input> to <output> (encryption mode)
|
||||||
|
-td <inp> <out> Process <input> to <output> (decryption mode)
|
||||||
|
-i Process in-place for non-specific files (i.e. do not add or remove `.rae` file extension)
|
||||||
|
|
||||||
|
-k <key> Use this key (autodetect type)
|
||||||
|
-kR <key> Use this key (rsa private)
|
||||||
|
-kr <key> Use this key (rsa public)
|
||||||
|
-ka <key> Use this key (aes)
|
||||||
|
Other options for `-k`
|
||||||
|
-p Read a password from stdin for this key
|
||||||
|
-P <password> The next argument after the key file is a password for the key
|
||||||
|
-s This key is also a signing key
|
||||||
|
-S This key is only a signing key
|
||||||
|
|
||||||
|
EXAMPLE:
|
||||||
|
{program} -kRP key.rsa "super secret password" -ka key.aes -te input_file output_encrypted file
|
||||||
|
"#,
|
||||||
|
pkg_version = env!("CARGO_PKG_VERSION"),
|
||||||
|
real_version = CURRENT_VERSION,
|
||||||
|
authour = env!("CARGO_PKG_AUTHORS"),
|
||||||
|
program = program_name());
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
/// Parse the program args into `Operation`.
|
||||||
|
pub async fn parse_args() -> Result<Spec, eyre::Report>
|
||||||
|
{
|
||||||
|
parse::parse(std::env::args().skip(1))
|
||||||
|
.with_suggestion(|| "Try passing `--help`")
|
||||||
|
}
|
@ -0,0 +1,301 @@
|
|||||||
|
//! Arg parsing
|
||||||
|
#![allow(unused_macros)]
|
||||||
|
use super::*;
|
||||||
|
use tokio::{
|
||||||
|
sync::{
|
||||||
|
mpsc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
iter,
|
||||||
|
};
|
||||||
|
use eyre::eyre;
|
||||||
|
use config::op::Password;
|
||||||
|
use error::ParseErrorKind;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum IsSigning
|
||||||
|
{
|
||||||
|
No,
|
||||||
|
Yes,
|
||||||
|
Only,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IsSigning
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum KeyKind
|
||||||
|
{
|
||||||
|
Autodetect,
|
||||||
|
RsaPublic,
|
||||||
|
RsaPrivate(IsSigning), // is signing key?
|
||||||
|
Aes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum TranslationMode
|
||||||
|
{
|
||||||
|
Autodetect,
|
||||||
|
Encrypt,
|
||||||
|
Decrypt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TranslationMode
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self::Autodetect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Atom
|
||||||
|
{
|
||||||
|
Key(KeyKind, String, Password),
|
||||||
|
|
||||||
|
NonSpecific,
|
||||||
|
FileAuto(String),
|
||||||
|
FileSpecific(String, String, TranslationMode),
|
||||||
|
//TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
pub async fn parse<I: IntoIterator<Item=String> + std::fmt::Debug>(args: I) -> Result<!/* TODO: What type do we make for this? */, eyre::Report>
|
||||||
|
{
|
||||||
|
let (mut tx, mut rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
macro_rules! unwrap_handler {
|
||||||
|
($handler:expr) => ($handler.await
|
||||||
|
.wrap_err_with(|| eyre!("Atom parser panicked")
|
||||||
|
.with_warning(|| "This usually indicates a bug in the program, not invalid input.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = args.into_iter();
|
||||||
|
let handler = if let Some(arg) = args.next()
|
||||||
|
{
|
||||||
|
match arg.to_lowercase().trim() {
|
||||||
|
"--generate" => todo!(),
|
||||||
|
"--help" => todo!(),
|
||||||
|
"--stat" => todo!(),
|
||||||
|
_ => {
|
||||||
|
let handler = tokio::spawn(async move {
|
||||||
|
//TODO: What container type do we make for this?
|
||||||
|
while let Some(atom) = rx.recv().await {
|
||||||
|
debug!("[Normal] recv atom: {:?}", atom);
|
||||||
|
// Construct `Normal` with atoms
|
||||||
|
}
|
||||||
|
todo!()
|
||||||
|
});
|
||||||
|
match parse_normal(std::iter::once(arg)
|
||||||
|
.chain(args.map(Into::into)), &mut tx).await
|
||||||
|
{
|
||||||
|
// The `handler` has dropped `rx` and returned an error, let unwrap_handler down there handle this.
|
||||||
|
Err(error::Error::AtomInternal) => Ok(()),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
.with_note(|| "In parsing of normal operation spec")?;
|
||||||
|
|
||||||
|
|
||||||
|
handler
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(eyre!("No arguments specified"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match unwrap_handler!(handler)? {
|
||||||
|
Err(error) => {
|
||||||
|
return Err(error)
|
||||||
|
.wrap_err_with(|| eyre!("Failed to construct operation from arguments"))
|
||||||
|
.with_note(|| "In constructing of operation from spec");
|
||||||
|
},
|
||||||
|
Ok(result) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse_normal<I: IntoIterator<Item=String>>(args: I, atoms: &mut mpsc::Sender<Atom>) -> Result<(), error::Error>
|
||||||
|
{
|
||||||
|
let mut args = args.into_iter();
|
||||||
|
let mut reading=true;
|
||||||
|
|
||||||
|
|
||||||
|
while let Some(arg) = args.next()
|
||||||
|
{
|
||||||
|
|
||||||
|
macro_rules! take_one {
|
||||||
|
($msg:literal $($tt:tt)*) => {
|
||||||
|
match args.next() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err((arg, ParseErrorKind::ExpectedArg(format!($msg $($tt)*))).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reading && arg.starts_with("-") {
|
||||||
|
let mut opt = arg.chars().skip(1);
|
||||||
|
match opt.next() {
|
||||||
|
Some('i') => match opt.next() {
|
||||||
|
None => atoms.send(Atom::NonSpecific).await?,
|
||||||
|
Some(r) => return Err((ParseErrorKind::BadTail('i', iter::once(r).chain(opt).collect()), arg).swap().into()),
|
||||||
|
},
|
||||||
|
Some('t') => {
|
||||||
|
let input = take_one!("t: Expected input file");
|
||||||
|
let output = take_one!("t: Expected output file");
|
||||||
|
let mut trans = TranslationMode::default();
|
||||||
|
|
||||||
|
let mut had = smallmap::Map::new();
|
||||||
|
while let Some(opt) = opt.next() {
|
||||||
|
if (had.contains_key(&opt), had.insert(opt, ())).0 {
|
||||||
|
// arg has been repeated, not allowed
|
||||||
|
return Err((arg, error::ParseErrorKind::NotUnique(opt)).into());
|
||||||
|
}
|
||||||
|
macro_rules! check_combine {
|
||||||
|
($($chars:expr),+) => {
|
||||||
|
$(if had.contains_key(&$chars) {
|
||||||
|
return Err(error::Error::from((arg, error::ParseErrorKind::CannotCombine(opt, $chars))));
|
||||||
|
})*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match opt {
|
||||||
|
'e' => trans= TranslationMode::Encrypt,
|
||||||
|
'd' => trans= TranslationMode::Decrypt,
|
||||||
|
_ => return Err((arg, error::ParseErrorKind::UnknownOptionFor('t', opt)).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atoms.send(Atom::FileSpecific(input, output, trans)).await?;
|
||||||
|
},
|
||||||
|
Some('k') => {
|
||||||
|
let key_file = take_one!("k: Expected a key file");
|
||||||
|
enum KeySpec {
|
||||||
|
Autodetect,
|
||||||
|
RsaPrivate,
|
||||||
|
RsaPublic,
|
||||||
|
Aes,
|
||||||
|
}
|
||||||
|
let mut kind = KeySpec::Autodetect;
|
||||||
|
let mut password = Password::default();
|
||||||
|
let mut signing = IsSigning::default();
|
||||||
|
|
||||||
|
let mut had = smallmap::Map::new();
|
||||||
|
while let Some(opt) = opt.next() {
|
||||||
|
if (had.contains_key(&opt), had.insert(opt, ())).0 {
|
||||||
|
// arg has been repeated, not allowed
|
||||||
|
return Err((arg, error::ParseErrorKind::NotUnique(opt)).into());
|
||||||
|
}
|
||||||
|
macro_rules! check_combine {
|
||||||
|
($($chars:expr),+) => {
|
||||||
|
$(if had.contains_key(&$chars) {
|
||||||
|
return Err(error::Error::from((arg, error::ParseErrorKind::CannotCombine(opt, $chars))));
|
||||||
|
})*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match opt {
|
||||||
|
'R' => {
|
||||||
|
check_combine!['r','a'];
|
||||||
|
kind = KeySpec::RsaPrivate;
|
||||||
|
},
|
||||||
|
'r' => {
|
||||||
|
check_combine!['R','a'];
|
||||||
|
kind = KeySpec::RsaPublic;
|
||||||
|
},
|
||||||
|
'a' => {
|
||||||
|
check_combine!['r','R'];
|
||||||
|
kind = KeySpec::Aes;
|
||||||
|
},
|
||||||
|
'P' => {
|
||||||
|
check_combine!['p'];
|
||||||
|
password= Password::Yes;
|
||||||
|
},
|
||||||
|
'p' => {
|
||||||
|
check_combine!['P'];
|
||||||
|
password = Password::Specific(take_one!("P: Expected a password"));
|
||||||
|
},
|
||||||
|
'S' => {
|
||||||
|
check_combine!['s', 'a'];
|
||||||
|
signing = IsSigning::Only;
|
||||||
|
},
|
||||||
|
's' => {
|
||||||
|
check_combine!['S', 'a'];
|
||||||
|
signing = IsSigning::Yes;
|
||||||
|
},
|
||||||
|
_ => return Err((arg, error::ParseErrorKind::UnknownOptionFor('k', opt)).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let kind = match kind {
|
||||||
|
KeySpec::Autodetect => KeyKind::Autodetect,
|
||||||
|
KeySpec::RsaPublic => KeyKind::RsaPublic,
|
||||||
|
KeySpec::RsaPrivate => KeyKind::RsaPrivate(signing),
|
||||||
|
KeySpec::Aes => KeyKind::Aes,
|
||||||
|
};
|
||||||
|
atoms.send(Atom::Key(kind, key_file, password)).await?;
|
||||||
|
},
|
||||||
|
Some(unknown) => return Err((arg, error::ParseErrorKind::UnknownOption(unknown)).into()),
|
||||||
|
None => reading=false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reading=false;
|
||||||
|
atoms.send(Atom::FileAuto(arg)).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
todo!()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[instrument]
|
||||||
|
pub fn parse<T: Into<String>, I: IntoIterator<Item = T> + std::fmt::Debug>(args: I) -> Result<Spec, eyre::Report>
|
||||||
|
{
|
||||||
|
let mut args = args.into_iter();
|
||||||
|
if let Some(arg) = args.next()
|
||||||
|
{
|
||||||
|
let arg =arg.into();
|
||||||
|
match arg.to_lowercase().trim() {
|
||||||
|
"--generate" => Ok(Spec::Concrete(config::Operation::GenerateKey(parse_keygen(args.map(Into::into))
|
||||||
|
.with_note(|| "Mode was `--generate`")?))),
|
||||||
|
"--help" => Ok(Spec::Concrete(config::Operation::Help)),
|
||||||
|
"--stat" => Ok(Spec::Concrete(config::Operation::KeyInfo(parse_stat(args.map(Into::into))
|
||||||
|
.with_note(|| "Mode was `--stat")?))),
|
||||||
|
_ => Ok(parse_normal(std::iter::once(arg)
|
||||||
|
.chain(args.map(Into::into)))
|
||||||
|
.with_note(|| "Mode was crypt")?.into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(eyre!("No arguments specified"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_normal<I: IntoIterator<Item = String>>(args: I) -> Result<OperationSpec, eyre::Report>
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_keygen<I: IntoIterator<Item = String>>(args: I) -> Result<config::op::GenerateKey, eyre::Report>
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_stat<I: IntoIterator<Item = String>>(args: I) -> Result<Vec<(String, config::op::Password)>, eyre::Report>
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
*/
|
@ -0,0 +1,94 @@
|
|||||||
|
//! Parsed config
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub mod op {
|
||||||
|
|
||||||
|
/// The crypt mode
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
|
pub enum Mode {
|
||||||
|
/// Detect based on file-type
|
||||||
|
Autodetect,
|
||||||
|
Encrypt,
|
||||||
|
Decrypt,
|
||||||
|
}
|
||||||
|
impl Default for Mode
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Mode::Autodetect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone,PartialEq, Eq, Default)]
|
||||||
|
pub struct Normal {
|
||||||
|
pub rsa: Vec<(String, Password)>,
|
||||||
|
pub sign: Vec<(String, Password)>,
|
||||||
|
pub aes: Vec<(String, Password)>,
|
||||||
|
pub mode: Mode,
|
||||||
|
|
||||||
|
pub files: Vec<(String, Option<String>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone,PartialEq, Eq)]
|
||||||
|
pub enum GenerateKey {
|
||||||
|
Rsa(Rsa),
|
||||||
|
Aes(Aes),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
|
pub enum Password
|
||||||
|
{
|
||||||
|
No,
|
||||||
|
Yes,
|
||||||
|
Specific(String),
|
||||||
|
}
|
||||||
|
impl Default for Password
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug ,PartialEq, Eq, Clone, Hash, Default)]
|
||||||
|
pub struct KeyDescription
|
||||||
|
{
|
||||||
|
pub name: String,
|
||||||
|
pub email: String,
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
other: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone,PartialEq, Eq)]
|
||||||
|
pub struct Rsa
|
||||||
|
{
|
||||||
|
input: Option<(String, Password)>,
|
||||||
|
|
||||||
|
output_priv: String,
|
||||||
|
output_pub: Option<String>,
|
||||||
|
password: Password,
|
||||||
|
|
||||||
|
description: KeyDescription,
|
||||||
|
}
|
||||||
|
#[derive(Debug,Clone, PartialEq, Eq)]
|
||||||
|
pub struct Aes
|
||||||
|
{
|
||||||
|
input: Option<(String, Password)>,
|
||||||
|
|
||||||
|
output: String,
|
||||||
|
password: Password,
|
||||||
|
|
||||||
|
description: KeyDescription,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone,PartialEq, Eq)]
|
||||||
|
pub enum Operation
|
||||||
|
{
|
||||||
|
Normal(op::Normal),
|
||||||
|
GenerateKey(op::GenerateKey),
|
||||||
|
KeyInfo(Vec<(String, op::Password)>),
|
||||||
|
Help,
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
//! Extensions
|
||||||
|
|
||||||
|
pub trait JoinStrsExt: Sized
|
||||||
|
{
|
||||||
|
/// Join an iterator of `str` with a seperator
|
||||||
|
fn join(self, with: &str) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T,I> JoinStrsExt for I
|
||||||
|
where I: Iterator<Item=T>,
|
||||||
|
T: AsRef<str>
|
||||||
|
{
|
||||||
|
fn join(self, with: &str) -> String
|
||||||
|
{
|
||||||
|
let mut output = String::new();
|
||||||
|
let mut first=true;
|
||||||
|
for string in self
|
||||||
|
{
|
||||||
|
if !first {
|
||||||
|
output.push_str(with);
|
||||||
|
}
|
||||||
|
let string = string.as_ref();
|
||||||
|
output.push_str(string);
|
||||||
|
first=false;
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*macro_rules! typed_swap {
|
||||||
|
(@ [] $($reversed:tt)*) => {
|
||||||
|
fn swap(self) -> ($($reversed)*);
|
||||||
|
};
|
||||||
|
(@ [$first:tt $($rest:tt)*] $($reversed:tt)*) => {
|
||||||
|
typed_swap!{@ [$($rest)*] $first $($reversed)*}
|
||||||
|
};
|
||||||
|
(@impl {$($body:tt)*} [] $($reversed:tt)*) => {
|
||||||
|
fn swap(self) -> ($($reversed)*)
|
||||||
|
{
|
||||||
|
$($body)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(@impl {$($body:tt)*} [$first:tt $($rest:tt)*] $($reversed:tt)*) => {
|
||||||
|
typed_swap!{@impl {$($body)*} [$($rest)*] $first $($reversed)*}
|
||||||
|
};
|
||||||
|
() => {};
|
||||||
|
({$($params:tt)*} $($rest:tt)*) => {
|
||||||
|
mod swap {
|
||||||
|
pub trait SwapTupleExt<$($params)*>: Sized
|
||||||
|
{
|
||||||
|
typed_swap!(@ [$($params)*]);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<$($params)*> SwapTupleExt<$($params)*> for ($($params)*)
|
||||||
|
{
|
||||||
|
typed_swap!(@impl {
|
||||||
|
todo!()
|
||||||
|
} [$($params)*]);
|
||||||
|
}
|
||||||
|
|
||||||
|
typed_swap!($($rest)*);
|
||||||
|
}
|
||||||
|
pub use swap::*;
|
||||||
|
};
|
||||||
|
|
||||||
|
(all $first:tt $($params:tt)+) => {
|
||||||
|
typed_swap!({$first, $($params),+});
|
||||||
|
mod nswap {
|
||||||
|
typed_swap!(all $($params)+);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(all $($one:tt)?) => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
typed_swap!(all A B C D E F G H I J K L M N O P Q R S T U V W X Y Z);
|
||||||
|
pub use swap::*;
|
||||||
|
|
||||||
|
fn test()
|
||||||
|
{
|
||||||
|
let sw = (1, 2).swap();
|
||||||
|
|
||||||
|
}*/
|
||||||
|
// ^ unfortunately not lol
|
||||||
|
|
||||||
|
pub trait SwapTupleExt<T,U>: Sized
|
||||||
|
{
|
||||||
|
fn swap(self) -> (U,T);
|
||||||
|
}
|
||||||
|
impl<T,U> SwapTupleExt<T,U> for (T,U)
|
||||||
|
{
|
||||||
|
#[inline(always)] fn swap(self) -> (U,T) {
|
||||||
|
(self.1, self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*typed_swap!({A, B}
|
||||||
|
{A, U, V}
|
||||||
|
{T, U, V, W});*/
|
@ -0,0 +1,86 @@
|
|||||||
|
#![cfg_attr(nightly, feature(label_break_value))]
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
#[macro_use] extern crate tracing;
|
||||||
|
use std::{
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
|
};
|
||||||
|
use color_eyre::{
|
||||||
|
eyre::{
|
||||||
|
self,
|
||||||
|
WrapErr,
|
||||||
|
},
|
||||||
|
Help, SectionExt,
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
pub const CURRENT_VERSION: version::Version = version::Version::new(0,0,0,version::Tag::Prerelease);
|
||||||
|
|
||||||
|
mod ext;
|
||||||
|
use ext::*;
|
||||||
|
mod version;
|
||||||
|
mod config;
|
||||||
|
mod args;
|
||||||
|
|
||||||
|
/*/// Dispatch params operations that can be handled at top level (i.e. `Help`)
|
||||||
|
async fn dispatch_args() -> Result<args::Operation, eyre::Report>
|
||||||
|
{
|
||||||
|
match args::parse_args().await
|
||||||
|
.with_section(move || std::env::args().skip(1).join("\n").header("Input args were:"))? {
|
||||||
|
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
macro_rules! cfg_debug {
|
||||||
|
(if {$($if:tt)*} else {$($else:tt)*}) => {
|
||||||
|
{
|
||||||
|
cfg_if::cfg_if!{
|
||||||
|
if #[cfg(debug_assertions)] {
|
||||||
|
$($if)*
|
||||||
|
} else {
|
||||||
|
$($else)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn install_tracing() {
|
||||||
|
use tracing_error::ErrorLayer;
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
let fmt_layer = fmt::layer().with_target(false);
|
||||||
|
let filter_layer = EnvFilter::try_from_default_env()
|
||||||
|
.or_else(|_| EnvFilter::try_new("info"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(filter_layer)
|
||||||
|
.with(fmt_layer)
|
||||||
|
.with(ErrorLayer::default())
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn work(op: config::Operation) -> Result<(), eyre::Report>
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), eyre::Report> {
|
||||||
|
install_tracing();
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
|
trace!("Parsing args");
|
||||||
|
let args = args::parse_args().await?;
|
||||||
|
|
||||||
|
work(args).await
|
||||||
|
.with_suggestion(|| "Run with `RUST_LOG=\"debug\"` environment variable for more detailed logging")
|
||||||
|
.with_suggestion(|| "Run with `RUST_LOG=\"trace\"` environment variable for extremely detailed logging")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,261 @@
|
|||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
path::{
|
||||||
|
PathBuf,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The type of key to generate
|
||||||
|
#[derive(Debug,Eq,PartialEq)]
|
||||||
|
pub enum GenerateKey
|
||||||
|
{
|
||||||
|
Rsa,
|
||||||
|
Aes,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The key serialise format
|
||||||
|
#[derive(Debug,Eq,PartialEq)]
|
||||||
|
pub enum KeyOutputFormat
|
||||||
|
{
|
||||||
|
Binary,
|
||||||
|
Text,
|
||||||
|
/// PEM format only valid for RSA keys
|
||||||
|
PEM,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for KeyOutputFormat
|
||||||
|
{
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match value.to_uppercase().as_str() {
|
||||||
|
"TEXT" => KeyOutputFormat::Text,
|
||||||
|
"BIN" => KeyOutputFormat::Binary,
|
||||||
|
"PEM" => KeyOutputFormat::PEM,
|
||||||
|
d => return Err(format!("Unknown key output format `{}`. Expected `TEXT`, `BIN` or `PEM`.", d))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyOutputFormat
|
||||||
|
{
|
||||||
|
pub fn is_aes_okay(&self) -> bool
|
||||||
|
{
|
||||||
|
if let KeyOutputFormat::PEM = self {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Operation mode for `Operation::Normal`
|
||||||
|
#[derive(Debug,Eq,PartialEq)]
|
||||||
|
pub enum OperationMode
|
||||||
|
{
|
||||||
|
Autodetect,
|
||||||
|
Encrypt,
|
||||||
|
Decrypt,
|
||||||
|
Verify,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The operation for this run
|
||||||
|
#[derive(Debug,Eq,PartialEq)]
|
||||||
|
pub enum Operation
|
||||||
|
{
|
||||||
|
/// Crypto
|
||||||
|
Normal{
|
||||||
|
mode: OperationMode,
|
||||||
|
rsa: Vec<PathBuf>,
|
||||||
|
sign: Vec<PathBuf>,
|
||||||
|
auto_sign: bool,
|
||||||
|
aes: Vec<PathBuf>,
|
||||||
|
|
||||||
|
translate: Vec<(PathBuf, Option<PathBuf>)>,
|
||||||
|
in_place: bool,
|
||||||
|
verbose: bool,
|
||||||
|
|
||||||
|
#[cfg(feature="threads")]
|
||||||
|
sync: bool,
|
||||||
|
},
|
||||||
|
/// Generate key
|
||||||
|
KeyGen{
|
||||||
|
key_type: GenerateKey,
|
||||||
|
format: KeyOutputFormat,
|
||||||
|
cycle: Option<PathBuf>,
|
||||||
|
use_password: bool,
|
||||||
|
password: Option<String>,
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
private_only: bool,
|
||||||
|
public_only: bool,
|
||||||
|
output_public: Option<PathBuf>,
|
||||||
|
verbose: bool,
|
||||||
|
|
||||||
|
},
|
||||||
|
/// Print help
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation
|
||||||
|
{
|
||||||
|
/// Is verbose logging enabled
|
||||||
|
pub fn verbose(&self) -> bool
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Normal { verbose, .. } |
|
||||||
|
Self::KeyGen { verbose, .. } => *verbose,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! transcribe {
|
||||||
|
($cond:expr => $if:expr; $else:expr) => {
|
||||||
|
if $cond {$if} else {$else}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
impl fmt::Display for Operation
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match &self {
|
||||||
|
Self::Help => writeln!(f, "Display help"),
|
||||||
|
Self::Normal{
|
||||||
|
mode,
|
||||||
|
rsa,
|
||||||
|
sign,
|
||||||
|
auto_sign,
|
||||||
|
aes,
|
||||||
|
in_place,
|
||||||
|
verbose,
|
||||||
|
#[cfg(feature="threads")]
|
||||||
|
sync,
|
||||||
|
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
write!(f, "Mode: ")?;
|
||||||
|
match mode {
|
||||||
|
OperationMode::Encrypt => writeln!(f, "Encrypt")?,
|
||||||
|
OperationMode::Decrypt => writeln!(f, "Decrypt")?,
|
||||||
|
OperationMode::Verify => writeln!(f, "Verify")?,
|
||||||
|
_ => writeln!(f, "Autodetected")?,
|
||||||
|
}
|
||||||
|
if rsa.len() > 0 {
|
||||||
|
writeln!(f, "RSA encryption enabled for {} keys.", rsa.len())?;
|
||||||
|
}
|
||||||
|
if sign.len() > 0 || *auto_sign {
|
||||||
|
writeln!(f, "Signing with {} keys.", sign.len() + transcribe!(*auto_sign => rsa.len(); 0))?;
|
||||||
|
}
|
||||||
|
writeln!(f, "AES encrypting with {} keys.", aes.len())?;
|
||||||
|
writeln!(f, " (will generate {} session keys.)",
|
||||||
|
transcribe!(aes.len() > 0 =>
|
||||||
|
aes.len() * transcribe!(rsa.len()>0 => rsa.len(); 1);
|
||||||
|
rsa.len()))?;
|
||||||
|
if *in_place {
|
||||||
|
writeln!(f, "In-place")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="threads")]
|
||||||
|
if *sync {
|
||||||
|
writeln!(f, "Single-threaded mode")?;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature="threads"))]
|
||||||
|
writeln!(f, "Single-threaded mode")?;
|
||||||
|
|
||||||
|
if *verbose {
|
||||||
|
writeln!(f, "Verbose mode")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Self::KeyGen {
|
||||||
|
key_type: GenerateKey::Rsa,
|
||||||
|
format,
|
||||||
|
cycle,
|
||||||
|
password,
|
||||||
|
use_password,
|
||||||
|
output,
|
||||||
|
verbose,
|
||||||
|
|
||||||
|
private_only,
|
||||||
|
output_public,
|
||||||
|
public_only,
|
||||||
|
} => {
|
||||||
|
writeln!(f, "Generate key: RSA")?;
|
||||||
|
writeln!(f, "Output format: {}", format)?;
|
||||||
|
if let Some(cycle) = cycle {
|
||||||
|
if output.is_some() || output_public.is_some() {
|
||||||
|
writeln!(f, "Input: {:?}", cycle)?;
|
||||||
|
} else {
|
||||||
|
writeln!(f, "In-place: {:?}", cycle)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if password.is_some() || *use_password {
|
||||||
|
writeln!(f, "Using password.")?;
|
||||||
|
}
|
||||||
|
if let Some(output) = output {
|
||||||
|
writeln!(f, "Outputting to: {:?}", output)?;
|
||||||
|
}
|
||||||
|
if let Some(output_public) = output_public {
|
||||||
|
if *public_only {
|
||||||
|
writeln!(f, "Outputting public only to {:?}", output_public)?;
|
||||||
|
} else {
|
||||||
|
writeln!(f, "Outputting public to {:?}", output_public)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *private_only {
|
||||||
|
writeln!(f, "No public output")?;
|
||||||
|
}
|
||||||
|
if *verbose {
|
||||||
|
writeln!(f, "Verbose logging")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Self::KeyGen {
|
||||||
|
key_type: GenerateKey::Aes,
|
||||||
|
format,
|
||||||
|
cycle,
|
||||||
|
password,
|
||||||
|
use_password,
|
||||||
|
output,
|
||||||
|
verbose,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
writeln!(f, "Generate key: AES")?;
|
||||||
|
writeln!(f, "Output format: {}", format)?;
|
||||||
|
if let Some(cycle) = cycle {
|
||||||
|
if output.is_some() {
|
||||||
|
writeln!(f, "Input: {:?}", cycle)?;
|
||||||
|
} else {
|
||||||
|
writeln!(f, "In-place: {:?}", cycle)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if password.is_some() || *use_password {
|
||||||
|
writeln!(f, "Using password.")?;
|
||||||
|
}
|
||||||
|
if let Some(output) = output {
|
||||||
|
writeln!(f, "Outputting to: {:?}", output)?;
|
||||||
|
}
|
||||||
|
if *verbose {
|
||||||
|
writeln!(f, "Verbose logging.")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for KeyOutputFormat
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "{}", match self {
|
||||||
|
Self::PEM => "PEM",
|
||||||
|
Self::Text => "TEXT",
|
||||||
|
Self::Binary => "BIN",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
use std::{
|
||||||
|
error,
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Message(String),
|
||||||
|
MessageStatic(&'static str),
|
||||||
|
FileNotFound(std::path::PathBuf),
|
||||||
|
BadPath(std::path::PathBuf),
|
||||||
|
ExpectedFile(std::path::PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error{}
|
||||||
|
impl fmt::Display for Error
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "failed to parse args: ")?;
|
||||||
|
match self {
|
||||||
|
Error::Message(message) => write!(f, "{}", message),
|
||||||
|
Error::MessageStatic(message) => write!(f, "{}", message),
|
||||||
|
Error::FileNotFound(file) => write!(f, "file not found: `{:?}`", file),
|
||||||
|
Error::BadPath(file) => write!(f, "bad path: `{:?}`", file),
|
||||||
|
Error::ExpectedFile(file) => write!(f, "expected a file: `{:?}`", file),
|
||||||
|
_ => write!(f, "unknown error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Error
|
||||||
|
{
|
||||||
|
fn from(string: String) -> Self
|
||||||
|
{
|
||||||
|
Self::Message(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Error
|
||||||
|
{
|
||||||
|
fn from(string: &'static str) -> Self
|
||||||
|
{
|
||||||
|
Self::MessageStatic(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
impl From<PathBuf> for Error
|
||||||
|
{
|
||||||
|
fn from(path: PathBuf) -> Self
|
||||||
|
{
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
Self::FileNotFound(path)
|
||||||
|
} else if path.is_dir() {
|
||||||
|
Self::ExpectedFile(path)
|
||||||
|
} else {
|
||||||
|
Self::BadPath(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
//! Argument and option hanlding
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
env::args,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
/// Program executable name
|
||||||
|
#[inline]
|
||||||
|
pub fn program() -> &'static str
|
||||||
|
{
|
||||||
|
lazy_static! {
|
||||||
|
static ref NAME: &'static str = Box::leak(args().next().unwrap().into_boxed_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
&NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print usage and exit with exit code 1.
|
||||||
|
pub fn usage() -> !
|
||||||
|
{
|
||||||
|
println!("naes: native file encryptor (prog. ver: {}, format ver: {})", env!("CARGO_PKG_VERSION"), crate::CURRENT_VERSION);
|
||||||
|
println!(" made by {} with <3", env!("CARGO_PKG_AUTHORS"));
|
||||||
|
println!(" licensed with GPL v3");
|
||||||
|
#[cfg(feature="threads")] println!(" (compiled with threaded support)");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("Usage: {} [OPTIONS...] [-e|-d|-v] [--] <files...>", program());
|
||||||
|
println!("Usage: {} --keygen RSA [--verbose] [--cycle <old private>] [--format TEXT|BIN|PEM] [--private-only|--public-only] [--password] <output> [<public output>]", program());
|
||||||
|
println!("Usage: {} --keygen AES [--verbose] [--cycle <old key>] [--format TEXT|BIN] [--password] <output>", program());
|
||||||
|
println!("Usage: {} --help", program());
|
||||||
|
println!();
|
||||||
|
println!("Options:");
|
||||||
|
println!(" --rsa[=keys,...] [<key>]\tEncrypt with this (or these) RSA keys.");
|
||||||
|
println!(" --sign[=keys,...] [<key>]\tSign with this (or these) RSA keys.");
|
||||||
|
println!(" --auto-sign\t\t\tWhen RSA encryption keys are provided, also sign with them.");
|
||||||
|
println!(" --aes[=keys,...] [<key>]\tEncrypt with this (or these) AES keys.");
|
||||||
|
println!(" --translate=[<in>,<out>] [<in> <out>]\tSpecify input and output filenames.");
|
||||||
|
println!(" --in-place\t\t\tDo not add/remove `.nenc` extension to/from output file.");
|
||||||
|
println!(" --verbose\t\t\tVerbose logging.");
|
||||||
|
#[cfg(feature="threads")]
|
||||||
|
println!(" --sync\t\t\tDo not process files concurrently.");
|
||||||
|
println!();
|
||||||
|
println!("Additional options:");
|
||||||
|
println!(" -e\t\tForce encryption mode. Default is autodetect.");
|
||||||
|
println!(" -d\t\tForce decryption mode. Default is autodetect.");
|
||||||
|
println!(" -v\t\tVerification mode. Checks integrity of encrypted files and validates signatures with provided signing keys.");
|
||||||
|
println!(" --\t\tStop reading args.");
|
||||||
|
println!();
|
||||||
|
println!("Keygen only options:");
|
||||||
|
println!(" --cycle <old key>\t\tRead <old key> and write out to output instead of generating new key.");
|
||||||
|
println!(" --format TEXT|BIN|PEM\t\tOutput key format. PEM is only valid for RSA outputs. Default is BIN.");
|
||||||
|
println!(" --private-only\t\tOnly valid for RSA outputs. Do not re-output public key.");
|
||||||
|
println!(" --public-only\t\t\tOnly valid for RSA outputs. Only output public key. Only useful with `--cycle`.");
|
||||||
|
println!(" --password[=text]\t\tPassword-protect this key.");
|
||||||
|
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
use error::*;
|
||||||
|
|
||||||
|
mod data;
|
||||||
|
pub use data::*;
|
||||||
|
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
mod validate;
|
||||||
|
|
||||||
|
mod spec;
|
||||||
|
|
||||||
|
/// Parse the command line arguments passed.
|
||||||
|
pub async fn parse_args() -> Result<Operation, Error>
|
||||||
|
{
|
||||||
|
let mut args = std::env::args();
|
||||||
|
let _ = args.next();
|
||||||
|
|
||||||
|
parse::parse(&mut args).await?.try_into_validated()
|
||||||
|
}
|
@ -0,0 +1,432 @@
|
|||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
convert::{
|
||||||
|
TryFrom,TryInto,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod builder {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(PartialEq,Eq,Debug)]
|
||||||
|
pub enum OperationMode
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
KeyGen(GenerateKey),
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq,Eq,Debug)]
|
||||||
|
pub struct KeyGenOps
|
||||||
|
{
|
||||||
|
pub format: KeyOutputFormat,
|
||||||
|
pub cycle: Option<String>,
|
||||||
|
pub use_password: bool,
|
||||||
|
pub password: Option<String>,
|
||||||
|
pub output: Option<String>,
|
||||||
|
pub private_only: bool,
|
||||||
|
pub output_public: Option<String>,
|
||||||
|
pub public_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for KeyGenOps
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
format: KeyOutputFormat::Binary,
|
||||||
|
cycle: None,
|
||||||
|
use_password: false,
|
||||||
|
password: None,
|
||||||
|
output: None,
|
||||||
|
private_only: false,
|
||||||
|
output_public: None,
|
||||||
|
public_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq,Eq,Debug)]
|
||||||
|
pub struct NormalOps
|
||||||
|
{
|
||||||
|
pub mode: super::super::OperationMode,
|
||||||
|
pub rsa: Vec<String>,
|
||||||
|
pub sign: Vec<String>,
|
||||||
|
pub auto_sign: bool,
|
||||||
|
pub aes: Vec<String>,
|
||||||
|
|
||||||
|
pub translate: Vec<(String, Option<String>)>,
|
||||||
|
pub in_place: bool,
|
||||||
|
pub sync: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NormalOps
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
mode: super::super::OperationMode::Autodetect,
|
||||||
|
rsa: Vec::new(),
|
||||||
|
sign: Vec::new(),
|
||||||
|
auto_sign: false,
|
||||||
|
aes: Vec::new(),
|
||||||
|
translate: Vec::new(),
|
||||||
|
in_place: false,
|
||||||
|
sync: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq,Eq,Debug)]
|
||||||
|
pub struct Operation
|
||||||
|
{
|
||||||
|
pub mode: OperationMode,
|
||||||
|
|
||||||
|
pub verbose: bool,
|
||||||
|
pub keygen: KeyGenOps,
|
||||||
|
pub normal: NormalOps,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Operation
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
mode: OperationMode::Help,
|
||||||
|
keygen: KeyGenOps::default(),
|
||||||
|
normal: Default::default(),
|
||||||
|
verbose: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split<'a>(input: &'a str) -> (&'a str, Option<&'a str>)
|
||||||
|
{
|
||||||
|
if let Some(index) = input.find('=') {
|
||||||
|
(&input[..index], Some(&input[(index+1)..]))
|
||||||
|
} else {
|
||||||
|
(input, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn parse<I,T>(input: &mut I) -> Result<Operation, Error>
|
||||||
|
where I: Iterator<Item=T>,
|
||||||
|
T: AsRef<str>
|
||||||
|
{
|
||||||
|
let mut i=0;
|
||||||
|
|
||||||
|
let mut op = builder::Operation::default();
|
||||||
|
let mut reading = true;
|
||||||
|
let mut rest = Vec::new();
|
||||||
|
let mut cfg_files = Vec::new();
|
||||||
|
|
||||||
|
macro_rules! take_one
|
||||||
|
{
|
||||||
|
($message:expr) => {(i+=1, input.next().ok_or_else(|| $message)?).1.as_ref()}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(arg) = input.next()
|
||||||
|
{
|
||||||
|
let arg = arg.as_ref();
|
||||||
|
'next: {
|
||||||
|
if i == 0 {
|
||||||
|
// First arg
|
||||||
|
match arg {
|
||||||
|
"--help" => return Ok(Operation::Help),
|
||||||
|
"--keygen" => {
|
||||||
|
op.keygen = builder::KeyGenOps::default();
|
||||||
|
|
||||||
|
op.mode = builder::OperationMode::KeyGen(match take_one!("Expected a key type").to_uppercase().as_str() {
|
||||||
|
"AES" => GenerateKey::Aes,
|
||||||
|
"RSA" => GenerateKey::Rsa,
|
||||||
|
d => return Err(format!("Unknown key type `{}`. Expected `RSA` or `AES`.",d))?,
|
||||||
|
});
|
||||||
|
break 'next;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
op.mode = builder::OperationMode::Normal;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match op.mode {
|
||||||
|
builder::OperationMode::KeyGen(GenerateKey::Rsa) => {
|
||||||
|
let (name, value) = split(arg);
|
||||||
|
|
||||||
|
'cont: {
|
||||||
|
match name.to_lowercase().as_str() {
|
||||||
|
"-c" => {
|
||||||
|
cfg_files.push(take_one!("Expected a filename").to_owned());
|
||||||
|
},
|
||||||
|
"--verbose" if value.is_some() => op.verbose = value.unwrap().to_lowercase().trim() == "true",
|
||||||
|
"--verbose" => op.verbose = true,
|
||||||
|
"--cycle" if value.is_some() => op.keygen.cycle = Some(value.unwrap().to_owned()),
|
||||||
|
"--cycle" => op.keygen.cycle = Some(take_one!("Expected a filename").to_owned()),
|
||||||
|
"--format" if value.is_some() => op.keygen.format = value.unwrap().parse()?,
|
||||||
|
"--format" => op.keygen.format = take_one!("Expected an output format").parse()?,
|
||||||
|
"--password" if value.is_some() => {
|
||||||
|
op.keygen.use_password = true;
|
||||||
|
op.keygen.password = Some(value.unwrap().to_owned());
|
||||||
|
},
|
||||||
|
"--password" => op.keygen.use_password = true,
|
||||||
|
"--private-only" => op.keygen.private_only = true,
|
||||||
|
"--public-only" => op.keygen.public_only = true,
|
||||||
|
_ => break 'cont,
|
||||||
|
}
|
||||||
|
break 'next;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
builder::OperationMode::KeyGen(GenerateKey::Aes) => {
|
||||||
|
let (name, value) = split(arg);
|
||||||
|
'cont: {
|
||||||
|
match name.to_lowercase().as_str() {
|
||||||
|
"--verbose" if value.is_some() => op.verbose = value.unwrap().to_lowercase().trim() == "true",
|
||||||
|
"--verbose" => op.verbose = true,
|
||||||
|
"--cycle" if value.is_some() => op.keygen.cycle = Some(value.unwrap().to_owned()),
|
||||||
|
"--cycle" => op.keygen.cycle = Some(take_one!("Expected a filename").to_owned()),
|
||||||
|
"--format" if value.is_some() => op.keygen.format = value.unwrap().parse()?,
|
||||||
|
"--format" => op.keygen.format = take_one!("Expected an output format").parse()?,
|
||||||
|
"--password" if value.is_some() => {
|
||||||
|
op.keygen.use_password = true;
|
||||||
|
op.keygen.password = Some(value.unwrap().to_owned());
|
||||||
|
},
|
||||||
|
"--password" => op.keygen.use_password = true,
|
||||||
|
_ => break 'cont,
|
||||||
|
}
|
||||||
|
break 'next;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder::OperationMode::Normal => {
|
||||||
|
let (name, value) = split(arg);
|
||||||
|
if reading {
|
||||||
|
'cont: {
|
||||||
|
match name.to_lowercase().as_str() {
|
||||||
|
"--rsa" if value.is_some() => op.normal.rsa.extend(value.unwrap().split(',').map(|x| x.to_owned())),
|
||||||
|
"--rsa" => op.normal.rsa.push(take_one!("Expected an RSA key filename").to_owned()),
|
||||||
|
"--sign" if value.is_some() => op.normal.sign.extend(value.unwrap().split(',').map(|x| x.to_owned())),
|
||||||
|
"--sign" => op.normal.sign.push(take_one!("Expected an RSA signing key filename").to_owned()),
|
||||||
|
"--auto-sign" => op.normal.auto_sign = true,
|
||||||
|
"--aes" if value.is_some() => op.normal.aes.extend(value.unwrap().split(',').map(|x| x.to_owned())),
|
||||||
|
"--aes" => op.normal.aes.push(take_one!("Expected an AES key filename").to_owned()),
|
||||||
|
"--translate" if value.is_some() => {
|
||||||
|
let mut value = value.unwrap().split(',');
|
||||||
|
let input = value.next().ok_or("Invalid --translate singleton")?.to_owned();
|
||||||
|
let output = value.next().ok_or("Invalid --translate singleton (expected an output)")?.to_owned();
|
||||||
|
op.normal.translate.push((input, Some(output)));
|
||||||
|
},
|
||||||
|
"--translate" => {
|
||||||
|
let input = take_one!("No --translate input").to_owned();
|
||||||
|
let output = take_one!("No --translate output").to_owned();
|
||||||
|
op.normal.translate.push((input.to_owned(), Some(output.to_owned())));
|
||||||
|
},
|
||||||
|
"--in-place" => op.normal.in_place = true,
|
||||||
|
"--verbose" => op.verbose = true,
|
||||||
|
"--sync" => op.normal.sync = true,
|
||||||
|
|
||||||
|
"-e" => {
|
||||||
|
op.normal.mode = super::OperationMode::Encrypt;
|
||||||
|
},
|
||||||
|
"-d" => {
|
||||||
|
op.normal.mode = super::OperationMode::Decrypt;
|
||||||
|
},
|
||||||
|
"-v" => {
|
||||||
|
op.normal.mode = super::OperationMode::Verify;
|
||||||
|
},
|
||||||
|
|
||||||
|
"--" => reading = false,
|
||||||
|
_ => {
|
||||||
|
reading = false;
|
||||||
|
break 'cont;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
break 'next;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => return Err("Try `--help`")?,
|
||||||
|
}
|
||||||
|
rest.push(arg.to_owned());
|
||||||
|
};
|
||||||
|
i+=1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add `rest`
|
||||||
|
match op.mode {
|
||||||
|
builder::OperationMode::Normal => {
|
||||||
|
if rest.len() < 1 {
|
||||||
|
return Err("Expected at least one input file")?;
|
||||||
|
}
|
||||||
|
op.normal.translate.extend(rest.into_iter().map(|x| (x, None)));
|
||||||
|
},
|
||||||
|
builder::OperationMode::KeyGen(GenerateKey::Aes) => {
|
||||||
|
if rest.len() > 1 {
|
||||||
|
return Err("Expected only one output file")?;
|
||||||
|
} else if rest.len() < 1 && op.keygen.cycle.is_none() {
|
||||||
|
return Err("Expected one output file or `--cycle`")?;
|
||||||
|
} else if rest.len()>0 {
|
||||||
|
op.keygen.output = Some(rest.into_iter().next().unwrap());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder::OperationMode::KeyGen(GenerateKey::Rsa) => {
|
||||||
|
if rest.len() > 2 {
|
||||||
|
return Err("Expected one or 2 output files")?;
|
||||||
|
} else if rest.len() < 1 && op.keygen.cycle.is_none() {
|
||||||
|
return Err("Expected at least one output or `--cycle`")?;
|
||||||
|
} else if rest.len() == 2 {
|
||||||
|
let mut iter = rest.into_iter();
|
||||||
|
|
||||||
|
if op.keygen.public_only {
|
||||||
|
return Err("Expected one public output")?;
|
||||||
|
} else {
|
||||||
|
op.keygen.output = Some(iter.next().unwrap());
|
||||||
|
op.keygen.output_public = Some(iter.next().unwrap());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut iter = rest.into_iter();
|
||||||
|
if op.keygen.public_only {
|
||||||
|
op.keygen.output_public = Some(iter.next().ok_or("`--public-only` requires one output.")?);
|
||||||
|
} else {
|
||||||
|
op.keygen.output = iter.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => return Err("No params specified. Try `--help`.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure valid
|
||||||
|
let mut op = op.try_into()?;
|
||||||
|
|
||||||
|
// Add spec
|
||||||
|
for spec in cfg_files.into_iter()
|
||||||
|
{
|
||||||
|
spec::Spec::re //TODO: Fuck this whole module. Just rewrite all of this crap shit FUCK
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl TryFrom<builder::Operation> for Operation
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(op: builder::Operation) -> Result<Self, Self::Error>
|
||||||
|
{
|
||||||
|
match op.mode {
|
||||||
|
builder::OperationMode::Normal => {
|
||||||
|
|
||||||
|
if op.keygen != builder::KeyGenOps::default() {
|
||||||
|
return Err("Invalid mode for params.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.normal.rsa.len() < 1 && op.normal.auto_sign {
|
||||||
|
warn!("`--auto-sign` here does nothing.");
|
||||||
|
}
|
||||||
|
if op.normal.aes.len() < 1 && op.normal.rsa.len() < 1 {
|
||||||
|
return Err("No keys specified.")?;
|
||||||
|
}
|
||||||
|
if op.normal.translate.len() < 1 {
|
||||||
|
return Err("No files specified.")?;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature="threads"))]
|
||||||
|
if op.normal.sync {
|
||||||
|
warn!("`--sync` here does nothing, binary not compiled with `threads` feature.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of inputs without specified outputs
|
||||||
|
let autodetect_inputs = op.normal.translate.iter().filter(|x| x.1.is_none()).count();
|
||||||
|
if op.normal.in_place && autodetect_inputs < 1 {
|
||||||
|
warn!("`--in-place` here does nothing, no autodetected outputs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Operation::Normal{
|
||||||
|
mode: op.normal.mode,
|
||||||
|
rsa: op.normal.rsa.into_iter().map(|x| x.into()).collect(),
|
||||||
|
sign: op.normal.sign.into_iter().map(|x| x.into()).collect(),
|
||||||
|
auto_sign: op.normal.auto_sign,
|
||||||
|
aes: op.normal.aes.into_iter().map(|x| x.into()).collect(),
|
||||||
|
translate: op.normal.translate.into_iter().map(|x| (x.0.into(), x.1.and_then(|y| Some(y.into())))).collect(),
|
||||||
|
in_place: op.normal.in_place,
|
||||||
|
verbose: op.verbose,
|
||||||
|
#[cfg(feature="threads")]
|
||||||
|
sync: op.normal.sync,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
builder::OperationMode::KeyGen(GenerateKey::Rsa) => {
|
||||||
|
if op.normal != builder::NormalOps::default() {
|
||||||
|
return Err("Invalid mode for params.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.keygen.public_only && op.keygen.private_only {
|
||||||
|
return Err("`--private-only` and `--public-only` will generate nothing.")?;
|
||||||
|
}
|
||||||
|
if op.keygen.public_only && op.keygen.cycle.is_none() {
|
||||||
|
warn!("`--public-only` is only useful with `--cycle`.");
|
||||||
|
}
|
||||||
|
if op.keygen.output.is_none()
|
||||||
|
&& op.keygen.output_public.is_none()
|
||||||
|
&& op.keygen.cycle.is_none() {
|
||||||
|
return Err("No output found.")?;
|
||||||
|
}
|
||||||
|
if op.keygen.public_only && op.keygen.cycle.is_none() && op.keygen.output_public.is_none()
|
||||||
|
{
|
||||||
|
return Err("`--public-only` requires one output or `--cycle`.")?;
|
||||||
|
}
|
||||||
|
if op.keygen.private_only && op.keygen.output_public.is_some()
|
||||||
|
{
|
||||||
|
warn!("Unused public key output.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Operation::KeyGen{
|
||||||
|
key_type: GenerateKey::Rsa,
|
||||||
|
format: op.keygen.format,
|
||||||
|
cycle: op.keygen.cycle.and_then(|x| Some(x.into())),
|
||||||
|
use_password: op.keygen.use_password,
|
||||||
|
output: op.keygen.output.and_then(|x| Some(x.into())),
|
||||||
|
private_only: op.keygen.private_only,
|
||||||
|
public_only: op.keygen.public_only,
|
||||||
|
output_public: op.keygen.output_public.and_then(|x| Some(x.into())),
|
||||||
|
password: op.keygen.password,
|
||||||
|
verbose: op.verbose,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
builder::OperationMode::KeyGen(GenerateKey::Aes) => {
|
||||||
|
if op.normal != builder::NormalOps::default() {
|
||||||
|
return Err("Invalid mode for params.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.keygen.private_only ||
|
||||||
|
op.keygen.public_only ||
|
||||||
|
op.keygen.output_public.is_some()
|
||||||
|
{
|
||||||
|
return Err("Invalid params for mode.")?;
|
||||||
|
}
|
||||||
|
if !op.keygen.format.is_aes_okay()
|
||||||
|
{
|
||||||
|
return Err("This output format is not valid for AES key.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if op.keygen.cycle.is_none() && op.keygen.output.is_none() {
|
||||||
|
return Err("No output set.")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Operation::KeyGen{
|
||||||
|
key_type: GenerateKey::Aes,
|
||||||
|
format: op.keygen.format,
|
||||||
|
cycle: op.keygen.cycle.and_then(|x| Some(x.into())),
|
||||||
|
use_password: op.keygen.use_password,
|
||||||
|
output: op.keygen.output.and_then(|x| Some(x.into())),
|
||||||
|
password: op.keygen.password,
|
||||||
|
verbose: op.verbose,
|
||||||
|
|
||||||
|
private_only: false,
|
||||||
|
public_only: false,
|
||||||
|
output_public: None,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
builder::OperationMode::Help => Ok(Operation::Help),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
//! Way of serialising multiple keys in file
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
path::{
|
||||||
|
PathBuf,
|
||||||
|
Path,
|
||||||
|
},
|
||||||
|
marker::Unpin,
|
||||||
|
fmt,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
prelude::*,
|
||||||
|
io::{
|
||||||
|
AsyncRead,
|
||||||
|
AsyncWrite,
|
||||||
|
},
|
||||||
|
fs::{
|
||||||
|
OpenOptions,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An error type for serialisation or deserialisation
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SerdeError
|
||||||
|
{
|
||||||
|
Serialise(toml::ser::Error),
|
||||||
|
Deserialise(toml::de::Error),
|
||||||
|
IO(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<toml::ser::Error> for SerdeError
|
||||||
|
{
|
||||||
|
fn from(from: toml::ser::Error) -> Self
|
||||||
|
{
|
||||||
|
Self::Serialise(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<toml::de::Error> for SerdeError
|
||||||
|
{
|
||||||
|
fn from(from: toml::de::Error) -> Self
|
||||||
|
{
|
||||||
|
Self::Deserialise(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for SerdeError
|
||||||
|
{
|
||||||
|
fn from(from: io::Error) -> Self
|
||||||
|
{
|
||||||
|
Self::IO(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for SerdeError
|
||||||
|
{
|
||||||
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
|
match &self {
|
||||||
|
Self::Serialise(s) => Some(s),
|
||||||
|
Self::Deserialise(d) => Some(d),
|
||||||
|
Self::IO(io) => Some(io),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SerdeError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Serialise(_) => write!(f, "writing failed"),
|
||||||
|
Self::Deserialise(_) => write!(f, "reading failed"),
|
||||||
|
Self::IO(_) => write!(f, "io error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Serialisable struct containing key file information
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct Spec
|
||||||
|
{
|
||||||
|
rsa: Vec<String>,
|
||||||
|
rsa_sign: Vec<String>,
|
||||||
|
sign_all: Option<bool>,
|
||||||
|
aes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Signers<'a>(&'a Spec, usize);
|
||||||
|
|
||||||
|
impl<'a> Iterator for Signers<'a>
|
||||||
|
{
|
||||||
|
type Item = &'a String;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
(if self.1 < self.0.rsa_sign.len() {
|
||||||
|
Some(&self.0.rsa_sign[self.1])
|
||||||
|
} else if self.0.is_sign_all() {
|
||||||
|
let rest = self.1 -self.0.rsa_sign.len();
|
||||||
|
if rest < self.0.rsa.len() {
|
||||||
|
Some(&self.0.rsa[rest])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}, self.1+=1).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let len = self.0.rsa_sign.len() + if self.0.is_sign_all() {
|
||||||
|
self.0.rsa.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
(len, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> std::iter::ExactSizeIterator for Signers<'a>{}
|
||||||
|
|
||||||
|
impl Spec
|
||||||
|
{
|
||||||
|
#[inline] fn is_sign_all(&self) -> bool
|
||||||
|
{
|
||||||
|
self.sign_all.unwrap_or(false)
|
||||||
|
}
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
rsa: Vec::new(),
|
||||||
|
rsa_sign: Vec::new(),
|
||||||
|
sign_all: Some(false),
|
||||||
|
aes: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all signers
|
||||||
|
pub fn signers(&self) -> Signers<'_>
|
||||||
|
{
|
||||||
|
Signers(self, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all RSA encryptors
|
||||||
|
pub fn rsa(&self) -> std::slice::Iter<'_, String>
|
||||||
|
{
|
||||||
|
self.rsa.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all AES encryptors
|
||||||
|
pub fn aes(&self) -> std::slice::Iter<'_, String>
|
||||||
|
{
|
||||||
|
self.aes.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume this instance into a `Normal` operation.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `op` is not `Normal` mode.
|
||||||
|
pub fn into_operation(self, op: &mut Operation)
|
||||||
|
{
|
||||||
|
match op {
|
||||||
|
Operation::Normal {
|
||||||
|
rsa,
|
||||||
|
aes,
|
||||||
|
sign,
|
||||||
|
auto_sign,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let sign_all = self.is_sign_all();
|
||||||
|
if *auto_sign == sign_all {
|
||||||
|
// Both are true or both are false, no extra conversion needed
|
||||||
|
sign.extend(self.rsa_sign.into_iter().map(PathBuf::from));
|
||||||
|
} else if sign_all {
|
||||||
|
// Need to add `rsa` and `rsa_sign` to `sign`.
|
||||||
|
sign.extend(self.rsa_sign.iter()
|
||||||
|
.chain(self.rsa.iter())
|
||||||
|
.cloned()
|
||||||
|
.map(PathBuf::from));
|
||||||
|
}
|
||||||
|
rsa.extend(self.rsa.into_iter().map(PathBuf::from));
|
||||||
|
aes.extend(self.aes.into_iter().map(PathBuf::from));
|
||||||
|
},
|
||||||
|
_ => panic!("Tried to consume into an operation of invalid type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an instance from a stream
|
||||||
|
pub async fn read_from_stream<S: AsyncRead + ?Sized + Unpin>(from: &mut S) -> eyre::Result<Self>
|
||||||
|
{
|
||||||
|
let mut output = Vec::with_capacity(4096);
|
||||||
|
async fn internal<S: AsyncRead + ?Sized + Unpin>(output: &mut Vec<u8>, from: &mut S) -> Result<Spec, SerdeError> {
|
||||||
|
let mut buffer = [0u8; 4096];
|
||||||
|
let mut read;
|
||||||
|
while {read = from.read(&mut buffer[..]).await?; read>0} {
|
||||||
|
output.extend_from_slice(&buffer[..read]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(toml::from_slice(&output[..])?)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal(&mut output, from).await
|
||||||
|
.with_section(move || String::from_utf8_lossy(&output[..]).to_string().header("Read contents was"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an instance from a file
|
||||||
|
pub async fn read_from_file<P: AsRef<Path>>(from: P) -> eyre::Result<Self>
|
||||||
|
{
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(from.as_ref()).await?;
|
||||||
|
Ok(Self::read_from_stream(&mut file).await
|
||||||
|
.with_section(move || format!("{:?}", from.as_ref()).header("Filename was"))?)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl Operation
|
||||||
|
{
|
||||||
|
/// Validate the options i/o.
|
||||||
|
pub fn try_into_validated(self) -> Result<Self, Error>
|
||||||
|
{
|
||||||
|
match &self {
|
||||||
|
Self::KeyGen{
|
||||||
|
key_type: GenerateKey::Aes,
|
||||||
|
|
||||||
|
output,
|
||||||
|
cycle,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if output == cycle {
|
||||||
|
warn!("Cycle output is the same as input.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(output) = output {
|
||||||
|
if output.is_dir() {
|
||||||
|
return Err(output.clone())?;
|
||||||
|
}
|
||||||
|
if output.exists() {
|
||||||
|
warn!("Output will be overwritten.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cycle) = cycle {
|
||||||
|
if !cycle.exists() || cycle.is_dir() {
|
||||||
|
return Err(cycle.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::KeyGen{
|
||||||
|
key_type: GenerateKey::Rsa,
|
||||||
|
|
||||||
|
output,
|
||||||
|
output_public,
|
||||||
|
cycle,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if output == output_public && output.is_some() {
|
||||||
|
return Err("Output and public key output are the same.")?;
|
||||||
|
}
|
||||||
|
if cycle == output && output.is_some() {
|
||||||
|
warn!("Cycle output is the same as input.");
|
||||||
|
}
|
||||||
|
if let Some(output) = output {
|
||||||
|
if output.is_dir() {
|
||||||
|
return Err(output.clone())?;
|
||||||
|
}
|
||||||
|
if output.exists() {
|
||||||
|
warn!("Output will be overwritten.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(output_public) = output_public {
|
||||||
|
if output_public.is_dir() {
|
||||||
|
return Err(output_public.clone())?;
|
||||||
|
}
|
||||||
|
if output_public.exists() {
|
||||||
|
warn!("Output for public will be overwritten.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(cycle) = cycle {
|
||||||
|
if !cycle.exists() || cycle.is_dir() {
|
||||||
|
return Err(cycle.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,203 @@
|
|||||||
|
//! Version information for the file container format, semver-like internal version format.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
mem::size_of,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents other states of this container format version
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Tag
|
||||||
|
{
|
||||||
|
None =0,
|
||||||
|
Prerelease =1,
|
||||||
|
Unstable =2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error used by `Tag` and `Version` when they parse invalid input.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParsingError;
|
||||||
|
impl error::Error for ParsingError{}
|
||||||
|
impl fmt::Display for ParsingError
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "An attempt was made to parse invalid Version input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Tag
|
||||||
|
{
|
||||||
|
/// Try to parse a `u8` as `Tag`.
|
||||||
|
fn try_from_u8(from: u8) -> Result<Self, ParsingError>
|
||||||
|
{
|
||||||
|
macro_rules! branches {
|
||||||
|
($($num:path),*) => {
|
||||||
|
match from {
|
||||||
|
$(
|
||||||
|
x if x == $num as u8 => $num,
|
||||||
|
)*
|
||||||
|
_ => return Err(ParsingError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(branches! {
|
||||||
|
Self::Prerelease,
|
||||||
|
Self::Unstable,
|
||||||
|
Self::None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Tag
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Prerelease => write!(f, "*"),
|
||||||
|
Self::Unstable => write!(f, "~"),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Tag
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a container version number in this format: `Major.Minor.Bugfix.Tag`
|
||||||
|
///
|
||||||
|
/// Should adhear to this principle
|
||||||
|
/// * Major - If different, fail parsing.
|
||||||
|
/// * Minor - If higher that current, fail parsing.
|
||||||
|
/// * Bugfix - If higher than current, warn user.
|
||||||
|
/// * Tag - Unused, but may represent things like prerelease or unstable
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Default)]
|
||||||
|
#[repr(packed, C)]
|
||||||
|
pub struct Version(u8,u8,u8,Tag);
|
||||||
|
|
||||||
|
impl fmt::Display for Version
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
write!(f, "{}.{}.{}{}", self.0, self.1, self.2, self.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Version
|
||||||
|
{
|
||||||
|
/// Is this read version compatable with a system of version `other`?
|
||||||
|
pub fn is_compat(&self, other: &Self) -> bool
|
||||||
|
{
|
||||||
|
self.0 == other.0 &&
|
||||||
|
self.1 <= other.1
|
||||||
|
}
|
||||||
|
/// Should we warn about parsing with this version when system version is `other`?
|
||||||
|
pub fn should_warn(&self, other: &Self) -> bool
|
||||||
|
{
|
||||||
|
self.2 > other.2 || self.3 == Tag::Unstable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new instance of `Version`.
|
||||||
|
pub const fn new(major: u8, minor: u8, bugfix: u8, tag: Tag) -> Self
|
||||||
|
{
|
||||||
|
Self(major,minor,bugfix,tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode as u32
|
||||||
|
pub fn as_u32(&self) -> u32
|
||||||
|
{
|
||||||
|
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
|
||||||
|
unsafe{*(self as *const Self as *const u32)}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode u32 as self, assuming `Tag` is valid.
|
||||||
|
pub unsafe fn from_u32_unchecked(from: u32) -> Self
|
||||||
|
{
|
||||||
|
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
|
||||||
|
*(&from as *const u32 as *const Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to parse a `u32` encoded `Version`.
|
||||||
|
pub fn try_from_u32(from: u32) -> Result<Self, ParsingError>
|
||||||
|
{
|
||||||
|
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
|
||||||
|
debug_assert_eq!(size_of::<Self>(), 4);
|
||||||
|
let parts = &from as *const u32 as *const u8;
|
||||||
|
|
||||||
|
macro_rules! index {
|
||||||
|
($array:ident, $index:expr) => {
|
||||||
|
unsafe{*($array.offset($index))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::new(index!(parts, 0),
|
||||||
|
index!(parts, 1),
|
||||||
|
index!(parts, 2),
|
||||||
|
Tag::try_from_u8(index!(parts, 3))?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Tag
|
||||||
|
{
|
||||||
|
type Error = ParsingError;
|
||||||
|
|
||||||
|
#[inline] fn try_from(from: u8) -> Result<Self, Self::Error>
|
||||||
|
{
|
||||||
|
Self::try_from_u8(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Tag> for u8
|
||||||
|
{
|
||||||
|
#[inline] fn from(from: Tag) -> Self
|
||||||
|
{
|
||||||
|
from as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for Version
|
||||||
|
{
|
||||||
|
type Error = ParsingError;
|
||||||
|
|
||||||
|
#[inline] fn try_from(from: u32) -> Result<Self, Self::Error>
|
||||||
|
{
|
||||||
|
Self::try_from_u32(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Version> for u32
|
||||||
|
{
|
||||||
|
#[inline] fn from(from: Version) -> Self
|
||||||
|
{
|
||||||
|
from.as_u32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests
|
||||||
|
{
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn vers_encode()
|
||||||
|
{
|
||||||
|
let version1 = Version::new(1,0,0,Tag::Unstable);
|
||||||
|
let version2 = Version::try_from_u32(version1.as_u32()).unwrap();
|
||||||
|
let version3 = unsafe{Version::from_u32_unchecked(version1.as_u32())};
|
||||||
|
assert_eq!(format!("{}", version1), "1.0.0~");
|
||||||
|
assert_eq!(version1, version2);
|
||||||
|
assert_eq!(version2, version3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue