use crate::colored::Colorize;
use crate::S3RS_CONFIG_FOLDER;
use std::fs;
use blake2_rfc::blake2b::blake2b;
use dirs::home_dir;
use hex;
use rand::{thread_rng, Rng};
use s3handler::CredentialConfig;
static FILTER: [u8; 3] = [10, 9, 13];
pub fn print_secret_usage() {
println!(
r#"
SECRET FEATURE
s3rs will use your secret to encrpt/decrypt you s3rs configure file to provide a better information security
{0} {1} {2}/{3}
Set up your secret to encrypt/decrpt s3 config, the scret can be generated by the secret phrases or hex literal.
for secret phrases example, you can use a sentence to be the secret key \"You are the apple of my eye\"
for hex literal example, \"0x596f752061726520746865206170706c65206f66206d7920657965\"
{0} {4}
clean your secret in memory
{0} {5} {6}
Encrypt current setting in to config.
{6} is optional, if {6} is not specific, the decrypted config wiil print on screen, else the
encrypted s3rs config file will be generated in S3RS_CONFIG_FOLDER, default is `~/.config/s3rs`
if there is any file with the same name, it will be overwritten
{0} {7} {6}
Show the current decrpted s3rs config file and print on screen
{6} is optional, if {6} is not specific, the pain config wiil print on screen, else the
pain s3rs config file will be generated in S3RS_CONFIG_FOLDER, default is `~/.config/s3rs`
if there is any file with the same name, it will be overwritten
If you have any issue, please submit to here https://github.com/yanganto/s3rs/issues
"#,
"secret".bold(),
"set".bold(),
"<secret phrases>".cyan(),
"<secret hex literal>",
"unset".bold(),
"encrypt".bold(),
"<config name>".cyan(),
"show".bold(),
)
}
pub fn do_command(
run_time_secret: &mut Vec<u8>,
command: &mut String,
config_list: &Vec<CredentialConfig>,
chosen_int: usize,
) {
if command.starts_with("set") {
change_secret(
run_time_secret,
command.strip_prefix("set").unwrap().trim().to_string(),
true,
);
} else if command.starts_with("unset") {
*run_time_secret = Vec::new();
println!("The secret is cleared from memory.");
} else if command.starts_with("encrypt") {
if run_time_secret.len() > 0 {
encrypt_config(
run_time_secret,
&config_list[chosen_int],
command.strip_prefix("encrypt"),
);
} else {
error!("There is not secret in runtime");
}
} else if command.starts_with("show") {
show_config(&config_list[chosen_int], command.strip_prefix("show"));
} else {
print_secret_usage()
}
}
pub fn change_secret(run_time_secret: &mut Vec<u8>, command: String, verbose: bool) {
let mut error = false;
let new_secret: Vec<u8> = if command.starts_with("0x") {
(2..command.len())
.step_by(2)
.map(|i| {
return match u8::from_str_radix(&command[i..i + 2], 16) {
Ok(b) => b,
Err(_) => {
error = true;
0u8
}
};
})
.collect()
} else {
let mut secret_phrases = Vec::new();
for p in command.split_whitespace() {
secret_phrases.push(p);
}
if secret_phrases.len() == 0 {
error = true;
Vec::new()
} else {
secret_phrases.join(" ").as_bytes().to_vec()
}
};
if new_secret.len() < 12 {
warn!("Please notice that your secret is too short!");
}
if error {
error!("generate secret error please check your hex string or secret phrase");
} else {
*run_time_secret = new_secret;
if verbose {
println!(
"new secret: {} (0x{})",
std::str::from_utf8(&run_time_secret.clone()).unwrap_or(""),
hex::encode(run_time_secret.clone())
);
println!("You can use Ctrl + D or logout to reload the config");
}
}
debug!(
"current secret: {} (0x{})",
std::str::from_utf8(&run_time_secret.clone()).unwrap_or(""),
hex::encode(run_time_secret)
);
}
pub fn encrypt_config(
run_time_secret: &mut Vec<u8>,
config: &CredentialConfig,
may_file_name: Option<&str>,
) {
let CredentialConfig {
access_key,
secret_key,
host,
s3_type,
region,
user,
secure,
} = config;
let mut config = format!(
r#"[[credential]]
host = "0x{}"
access_key = "0x{}"
secret_key = "0x{}"
"#,
xor_by_secret(
&mut SecretGenerator::new(run_time_secret, "host"),
host.to_string().into_bytes()
),
xor_by_secret(
&mut SecretGenerator::new(run_time_secret, "access_key"),
access_key.to_string().into_bytes()
),
xor_by_secret(
&mut SecretGenerator::new(run_time_secret, "secret_key"),
secret_key.to_string().into_bytes()
),
);
if secure.unwrap_or(false) {
config = format!("{}secure = true\n", config);
}
if let Some(u) = user {
config = format!(
"{}user = \"0x{}\"\n",
config,
xor_by_secret(
&mut SecretGenerator::new(run_time_secret, "user"),
u.to_string().into_bytes()
)
);
}
if let Some(t) = s3_type {
config = format!("{}s3_type = \"{}\"\n", config, t);
}
if let Some(r) = region {
config = format!(
"{}region = \"0x{}\"\n",
config,
xor_by_secret(
&mut SecretGenerator::new(run_time_secret, "region"),
r.clone().into_bytes()
),
);
}
let file_name = if let Some(f) = may_file_name {
f.trim()
} else {
""
};
if file_name.len() > 1 {
let mut config_path = home_dir()
.unwrap()
.join(S3RS_CONFIG_FOLDER)
.join(file_name.trim());
config_path.set_extension("toml");
if let Err(e) = fs::write(&config_path, config) {
error!(
"Fail to encrypt config to {}: {}",
config_path.as_path().display().to_string(),
e
);
} else {
println!("Current config file is encrypted and saved.");
}
} else {
println!("{}", config);
}
}
pub fn show_config(config: &CredentialConfig, may_file_name: Option<&str>) {
let CredentialConfig {
access_key,
secret_key,
host,
s3_type,
region,
user,
secure,
} = config;
let mut config = format!(
r#"[[credential]]
host = "{}"
access_key = "{}"
secret_key = "{}"
"#,
host, access_key, secret_key,
);
if secure.unwrap_or(false) {
config = format!("{}secure = true\n", config);
}
if let Some(u) = user {
config = format!("{}user = \"{}\"\n", config, u);
}
if let Some(t) = s3_type {
config = format!("{}s3_type = \"{}\"\n", config, t);
}
if let Some(r) = region {
config = format!("{}region = \"{}\"\n", config, r);
}
if let Some(file_name) = may_file_name {
let mut config_path = home_dir()
.unwrap()
.join(S3RS_CONFIG_FOLDER)
.join(file_name.trim());
config_path.set_extension("toml");
if let Err(e) = fs::write(&config_path, config) {
error!(
"Fail to decrypt config to {}: {}",
config_path.as_path().display().to_string(),
e
);
}
} else {
println!("{}", config);
}
}
pub fn decrypt_config(run_time_secret: &Vec<u8>, config: &mut CredentialConfig) {
let CredentialConfig {
access_key,
secret_key,
host,
region,
user,
..
} = config;
if access_key.starts_with("0x") {
config.access_key = decrypt_by_secret(
&mut SecretGenerator::new(run_time_secret, "access_key"),
access_key[2..].to_string(),
);
}
if secret_key.starts_with("0x") {
config.secret_key = decrypt_by_secret(
&mut SecretGenerator::new(run_time_secret, "secret_key"),
secret_key[2..].to_string(),
);
}
if host.starts_with("0x") {
config.host = decrypt_by_secret(
&mut SecretGenerator::new(run_time_secret, "host"),
host[2..].to_string(),
);
}
if let Some(r) = region {
if r.starts_with("0x") {
config.region = Some(decrypt_by_secret(
&mut SecretGenerator::new(run_time_secret, "region"),
r[2..].to_string(),
));
}
}
if let Some(u) = user {
if u.starts_with("0x") {
config.user = Some(decrypt_by_secret(
&mut SecretGenerator::new(run_time_secret, "user"),
u[2..].to_string(),
));
}
}
}
fn xor_by_secret(secret_generator: &mut SecretGenerator, target: Vec<u8>) -> String {
let mut rng = thread_rng();
let target_len = target.len();
let mut target = target;
let mut mixed_target = vec![128];
mixed_target.append(&mut target);
mixed_target.rotate_left(rng.gen_range(0, target_len));
if mixed_target.len() < 256 {
for _ in 0..(256 - mixed_target.len()) {
let r = rng.gen_range(0, mixed_target.len());
mixed_target.insert(r, *FILTER.iter().nth(r % FILTER.len()).unwrap());
}
}
for b in mixed_target.iter_mut() {
*b = *b
^ secret_generator
.next()
.expect("field to encrypt is too long")
}
hex::encode(mixed_target)
}
fn decrypt_by_secret(secret_generator: &mut SecretGenerator, target: String) -> String {
let mut t = hex::decode(target).unwrap_or_default();
for b in t.iter_mut() {
*b = *b
^ secret_generator
.next()
.expect("field to decrypt is too long")
}
let idx = t.iter().position(|&b| b == 128).unwrap_or(0);
t.rotate_left(idx);
t.remove(0);
let t = t.into_iter().filter(|c| !FILTER.contains(c)).collect();
String::from_utf8(t).unwrap()
}
struct SecretGenerator<'a> {
seed: &'a Vec<u8>,
field: &'static str,
counter: Option<u64>,
current_secrete_round: u8,
secret: [u8; 32],
}
impl<'a> SecretGenerator<'a> {
fn new(seed: &'a Vec<u8>, field: &'static str) -> Self {
let mut secret = [0; 32];
let mut data: Vec<u8> = (*seed).clone();
data.append(&mut field.to_string().into_bytes());
data.push(0u8);
secret.copy_from_slice(blake2b(32, &[], &data[..]).as_bytes());
SecretGenerator {
seed,
field,
counter: None,
current_secrete_round: 0u8,
secret,
}
}
}
impl<'a> Iterator for SecretGenerator<'a> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
let (counter, round, idx) = if let Some(c) = self.counter {
if c == 32u64 * u8::max_value() as u64 {
return None;
}
(c + 1, (c + 1) / 32, (c + 1) % 32)
} else {
(0, 0, 0)
};
if round != self.current_secrete_round as u64 {
self.current_secrete_round += 1;
let mut data: Vec<u8> = (*self.seed).clone();
data.append(&mut self.field.to_string().into_bytes());
data.push(self.current_secrete_round);
self.secret
.copy_from_slice(blake2b(32, &[], &data[..]).as_bytes());
}
self.counter = Some(counter);
Some(self.secret[idx as usize])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrpt_and_decrypt_small_str() {
let message = "a small string";
let key = "A apple a day keep the doctor away"
.to_string()
.into_bytes();
let encrypt_message = xor_by_secret(
&mut SecretGenerator::new(&key, "field name"),
message.to_string().into_bytes(),
);
assert_eq!(
decrypt_by_secret(
&mut SecretGenerator::new(&key, "field name"),
encrypt_message
),
message
);
}
#[test]
fn test_encrpt_and_decrypt_super_long_str() {
// 1000 chars, this cipher should works for string less than 8161
let message = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let key = "A apple a day keep the doctor away"
.to_string()
.into_bytes();
let encrypt_message = xor_by_secret(
&mut SecretGenerator::new(&key, "field name"),
message.to_string().into_bytes(),
);
assert_eq!(
decrypt_by_secret(
&mut SecretGenerator::new(&key, "field name"),
encrypt_message
),
message
);
}
}