Verified Commit b14c64d4 authored by Katharina Fey's avatar Katharina Fey 🏴
Browse files

Restructuring basic CLI

parent 85c01501
......@@ -326,14 +326,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.32.0"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -599,7 +599,7 @@ dependencies = [
"actix-web 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"keybob 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"miscreant 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -1410,7 +1410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
......@@ -1456,7 +1456,7 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -1890,7 +1890,7 @@ name = "yaml-rust"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
......@@ -1931,7 +1931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa"
"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum cmac 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f4a435124bcc292eba031f1f725d7abacdaf13cbf9f935450e8c45aa9e96cad"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
......@@ -2057,13 +2057,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum stream-cipher 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8861bc80f649f5b4c9bd38b696ae9af74499d479dbfb327f0607de6b326a36bc"
"checksum string 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98998cced76115b1da46f63388b909d118a37ae0be0f82ad35773d4a4bc9d18d"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
"checksum subtle 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "702662512f3ddeb74a64ce2fbbf3707ee1b6bb663d28bb054e0779bbc720d926"
"checksum syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)" = "734ecc29cd36e8123850d9bf21dfd62ef8300aaa8f879aabaa899721808be37c"
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum tokio 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4790d0be6f4ba6ae4f48190efa2ed7780c9e3567796abdb285003cf39840d9c5"
......
......@@ -5,12 +5,12 @@ authors = ["Katharina Fey <kookie@spacekookie.de>"]
edition = "2018"
[dependencies]
actix-web = "*"
clap = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
actix-web = "0.7"
clap = "2.33"
miscreant = { version = "0.4", features = ["soft-aes"] }
blake2 = "0.7"
keybob = "0.3"
base64 = "*"
base64 = "0.10"
use clap::{App, AppSettings, Arg, SubCommand};
use forge::{secret, setup};
use forge::{hash, secret, setup};
fn main() {
let app = App::new("forge-cli")
......@@ -12,35 +12,51 @@ fn main() {
.about("Create embeddable configuration secrets")
.version(secret::VERSION)
.arg(
Arg::with_name("SECRET")
Arg::with_name("KEY")
.required(true)
.takes_value(true)
.short("k")
.long("key")
.help("A secret to use as a key"),
)
.arg(
Arg::with_name("SALT")
.required(true)
.takes_value(true)
.short("s")
.long("secret")
.help("A secret that can remain secret"),
).arg(
Arg::with_name("DATA")
.long("salt")
.help("The salt to use for hash operations"),
),
)
.subcommand(
SubCommand::with_name("hash")
.about("Use a secret token to crate a public verification token")
.version(hash::VERSION)
.arg(
Arg::with_name("SALT")
.required(true)
.takes_value(true)
.help("The thing to encrypt and store"),
).arg(
Arg::with_name("CLI_FLAG")
.short("c")
.long("cli")
.help("Provide data via CLI options, instead of stdin or env"),
)
.arg(Arg::with_name("ENVIRONMENT")
.short("e")
.long("env")
.help("Provide all data via env variables instead of cli or STDIN")
.short("s")
.long("salt")
.help("The salt to use for hash operations"),
),
).subcommand(
)
.subcommand(
SubCommand::with_name("setup")
.about("A guided setup of the forge server (recommended)")
.version(setup::VERSION),
.about("Generate a skeleton `forge.yml` configuration")
.version(setup::VERSION)
.arg(
Arg::with_name("file")
.short("f")
.long("file")
.help("Provide a file name"),
),
);
match app.get_matches().subcommand() {
("setup", _) => unimplemented!(),
("hash", Some(m)) => hash::run(m),
("encrypt", Some(m)) => secret::run(m),
("setup", Some(m)) => setup::run(m),
_ => unreachable!(),
}
}
use clap::ArgMatches;
use std::io::{stdin, Read};
mod utils {
pub use crate::*;
}
/// The module version
pub const VERSION: &'static str = "0.1.0";
pub fn run(args: &ArgMatches<'_>) {
let salt = args.value_of("SALT").unwrap().into();
// Read token from stdin
let mut data = Vec::new();
stdin()
.read_to_end(&mut data)
.expect("Failed to read from STDIN");
let string = String::from_utf8(data)
.unwrap_or_else(|_| utils::log_fatal("Provided invalid UTF-8 data!"));
// Hash token twice with random salt
let hash = utils::blake2(string.as_str(), salt);
let hash_enc = utils::base64_encode(&hash.0.iter().map(|i| *i).collect());
let token = utils::blake2(hash_enc.as_str(), hash.1);
// Turn token to string that includes the salt
let token_str = format!("{}", token);
// Base64 encode and print
let encoded = utils::base64_encode(&token_str.into_bytes());
println!("{}", encoded);
}
pub mod hash;
pub mod secret;
pub mod setup;
......@@ -11,25 +12,57 @@ use base64;
use serde::{Deserialize, Serialize};
use serde_yaml;
use std::fmt::{self, Display, Formatter};
const BLAKE_16_LENGTH: usize = 16;
const SALT_DIVIDER: &'static str = "=0w0=";
/// A blake16 hash, consisting of a byte array
pub type Hash = [u8; BLAKE_16_LENGTH];
/// A blake16 hash, consisting of a byte array and salt
pub struct Hash<'salt>([u8; BLAKE_16_LENGTH], &'salt str);
impl<'salt> Display for Hash<'salt> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}{}{}", self.0, SALT_DIVIDER, self.1)
}
}
impl<'salt> From<&'salt String> for Hash<'salt> {
fn from(s: &'salt String) -> Self {
let split: Vec<_> = s.split(SALT_DIVIDER).collect();
let hash = split.get(0).unwrap().as_bytes().into_iter().zip(0..).fold(
[0; BLAKE_16_LENGTH],
|mut acc, (c, i)| {
acc[i] = *c;
acc
},
);
let salt = split.get(1).unwrap();
Hash(hash, salt)
}
}
/// Some enrypted data
pub struct PackedData {
pub nonce: Vec<u8>,
pub iv: Vec<u8>,
pub data: Vec<u8>,
}
/// Hash a value with blake2
pub fn blake2(data: &str, salt: &str) -> Hash {
/// Small utility to print nice user messags and exit
fn log_fatal(msg: &str) -> ! {
eprintln!("{}", msg);
std::process::exit(2);
}
/// Hash a salted value with blake2
pub fn blake2<'salt>(data: &str, salt: &'salt str) -> Hash<'salt> {
let mut hasher = match Blake2s::new(BLAKE_16_LENGTH) {
Ok(res) => res,
Err(some) => panic!(some),
};
let to_hash = format!("{}{}", data, salt);
let to_hash = format!("{}{}{}", data, SALT_DIVIDER, salt);
hasher.process(to_hash.as_bytes());
let mut buffer = [0u8; BLAKE_16_LENGTH];
......@@ -38,34 +71,25 @@ pub fn blake2(data: &str, salt: &str) -> Hash {
Err(e) => panic!(e),
};
return buffer;
}
pub fn verify_token_hash(rec: Vec<Hash>, given: Hash) -> Result<(), ()> {
// Iterator API is the hill I will die on
rec.iter()
.map(|rec| rec == &given)
.map(|b| if b { Ok(()) } else { Err(()) })
.fold(Err(()), |mut acc, x| {
if x.is_ok() {
acc = x
};
acc
})
}
/// Take a hashed secret and turn it into the ACTUAL encryption key
pub fn make_key(hashed_token: Hash, salt: &str) -> Key {
let hashed2 = blake2(
unsafe { ::std::str::from_utf8_unchecked(&hashed_token) },
salt,
);
Key::from_pw(
KeyType::Aes256,
unsafe { ::std::str::from_utf8_unchecked(&hashed2) },
salt,
)
Hash(buffer, salt)
}
/// Verify a secret token against a verification token
pub fn blake2_verify(hash: Hash, secret: String) -> bool {
let verify = blake2(secret.as_str(), hash.1);
hash.0
.iter()
.zip(verify.0.iter())
.fold(false, |acc, (a, b)| (a == b) && acc)
}
/// Derive a key from a secret token
pub fn derive_key(secret: String, salt: String) -> Key {
Key::from_pw(KeyType::Aes256, secret.as_str(), salt.as_str())
}
pub fn generate_salt() -> String {
"".into()
}
/// Decrypt some packed secret
......@@ -74,7 +98,8 @@ pub fn decrypt(key: &Key, data: PackedData) -> Vec<u8> {
ctx.open(
vec![data.nonce.as_slice(), data.iv.as_slice()],
data.data.as_slice(),
).unwrap()
)
.unwrap()
}
/// Encrypt some data into a packed secret
......@@ -131,10 +156,13 @@ impl From<YamlData> for PackedData {
pub fn to_yaml(pd: PackedData) -> String {
let yd: YamlData = pd.into();
serde_yaml::to_string(&yd).unwrap()
let s = serde_yaml::to_string(&yd).unwrap();
base64_encode(&s.into_bytes())
}
pub fn from_yaml(yaml: String) -> PackedData {
let tmp = base64_decode(&yaml);
let yaml = String::from_utf8(tmp).unwrap();
let yd: YamlData = serde_yaml::from_str(&yaml).unwrap();
yd.into()
}
//! The `secret` utility module which hashes tokens and encrypts user keys
use clap::ArgMatches;
use std::env;
use std::io::{stdin, stdout, Read, Write};
mod utils {
pub use crate::*;
}
struct Operands {
data: Vec<u8>,
secret: String,
}
/// The module version
pub const VERSION: &'static str = "0.1.0";
/// Run the `secret` module
pub fn run(args: &ArgMatches<'_>) {
let from_env = args.is_present("ENVIRONMENT");
let from_cli = args.is_present("CLI_FLAG");
// Either we read from STDIN or the user provided a DATA (or not)
let op: Operands = if from_cli {
let secret = args.value_of("SECRET").unwrap().into();
let data = args
.value_of("DATA")
.expect("Not reading from STDIN but not providing DATA")
.into();
Operands { data, secret }
} else if from_env {
Operands {
data: env::var("FORGE_DATA")
.expect("Env variable `FORGE_DATA` not found")
.as_bytes()
.into(),
secret: env::var("FORGE_SECRET").expect("Env variable `FORGE_SECRET` not found!"),
}
} else {
let secret = args.value_of("SECRET").unwrap().into();
let mut data = Vec::new();
stdin()
.read_to_end(&mut data)
.expect("Failed to read from STDIN");
Operands { data, secret }
};
let secret = args.value_of("SECRET").unwrap().into();
let salt = args.value_of("SALT").unwrap().into();
/* hash secret, make key, then encrypt DATA */
let hashed = utils::blake2(&op.secret, "forgectl");
let key = utils::make_key(hashed, "forgectl");
let mut data = Vec::new();
stdin()
.read_to_end(&mut data)
.expect("Failed to read from STDIN");
let encrypted = utils::encrypt(&key, op.data);
/* Create key from secret, then encrypt DATA */
let key = utils::derive_key(secret, salt);
let encrypted = utils::encrypt(&key, data);
let encoded = utils::to_yaml(encrypted);
stdout()
......
use clap::ArgMatches;
/// The module version
pub const VERSION: &'static str = "0.1.0";
pub fn run() {
pub fn run(args: &ArgMatches<'_>) {
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment