-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from kruserr/i6-pack
pack command
- Loading branch information
Showing
19 changed files
with
1,723 additions
and
111 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,19 @@ | ||
[package] | ||
name = "i6" | ||
version = "0.1.9" # prepare_release.sh | ||
edition = "2021" | ||
license = "AGPL-3.0" | ||
authors = ["kruserr"] | ||
readme = "README.md" | ||
repository = "https://github.com/kruserr/i6" | ||
description = "A collection of tools" | ||
keywords = ["cli", "terminal", "utility", "tool", "command"] | ||
categories = ["command-line-interface", "command-line-utilities", "development-tools"] | ||
[workspace] | ||
resolver = "2" | ||
|
||
[dependencies] | ||
clap = "3" | ||
members = [ | ||
"i6-pack", | ||
"i6", | ||
|
||
# for http and https commands | ||
tokio = { version = "1", features = ["full"] } | ||
# warp = "0.3" | ||
tracing-subscriber = "0.3" | ||
# for https command | ||
warp = { version = "0.3", features = ["default", "tls"] } | ||
openssl = "0.10" | ||
# Internal | ||
# "examples", | ||
] | ||
|
||
# for db command | ||
rapiddb-web = "0.1" | ||
|
||
[lints.rust] | ||
[workspace.lints.rust] | ||
unused_parens = "allow" | ||
unused_imports = "allow" | ||
|
||
[lints.clippy] | ||
[workspace.lints.clippy] | ||
needless_return = "allow" | ||
implicit_saturating_sub = "allow" | ||
single_component_path_imports = "allow" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "i6-pack" | ||
version = "0.1.10" | ||
edition = "2021" | ||
publish = false | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[dependencies] | ||
clap = "4" | ||
tar = "0.4" | ||
zstd = "0.13" | ||
aes-gcm = "0.10" | ||
rand = "0.8" | ||
hmac = "0.12" | ||
uuid = {version = "1", features = ["v4"]} | ||
argon2 = "0.5" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
use crate::compression; | ||
use crate::encryption; | ||
use crate::utils; | ||
|
||
pub fn run(action: &str, target: &str, password: &str) -> std::io::Result<()> { | ||
// Validate and sanitize the target path | ||
let target_path = utils::validate_path(target) | ||
.or_else(|_| utils::sanitize_output_path(target)) | ||
.expect("Invalid target path"); | ||
|
||
let tar_file = | ||
&format!(".{}_{}.tar", target_path.display(), uuid::Uuid::new_v4()); | ||
let compressed_file = | ||
&format!(".{}_{}.tar.zst", target_path.display(), uuid::Uuid::new_v4()); | ||
let encrypted_file = &format!("{}.i6p", target_path.display()); | ||
|
||
match action { | ||
"pack" => { | ||
compression::create_tar_archive(target_path.to_str().unwrap(), tar_file)?; | ||
compression::compress_tar_file(tar_file, compressed_file)?; | ||
encryption::encrypt_file(compressed_file, encrypted_file, password)?; | ||
} | ||
"unpack" => { | ||
encryption::decrypt_file( | ||
target_path.to_str().unwrap(), | ||
compressed_file, | ||
password, | ||
)?; | ||
compression::decompress_file(compressed_file, tar_file)?; | ||
compression::extract_tar_archive( | ||
tar_file, | ||
&utils::remove_extension(target_path.to_str().unwrap(), ".i6p"), | ||
)?; | ||
} | ||
_ => { | ||
eprintln!("Invalid action. Use 'pack' or 'unpack'."); | ||
std::process::exit(1); | ||
} | ||
} | ||
|
||
// Clean up temporary files | ||
std::fs::remove_file(tar_file)?; | ||
std::fs::remove_file(compressed_file)?; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use std::fs::File; | ||
use std::io::{self, Write}; | ||
use std::path::Path; | ||
use tar::Builder; | ||
use zstd::stream::{decode_all, encode_all}; | ||
|
||
pub fn create_tar_archive<P: AsRef<Path>>( | ||
folder: P, | ||
tar_file: &str, | ||
) -> io::Result<()> { | ||
let tar_gz = File::create(tar_file)?; | ||
let mut archive = Builder::new(tar_gz); | ||
archive.append_dir_all(".", folder)?; | ||
Ok(()) | ||
} | ||
|
||
pub fn extract_tar_archive(tar_file: &str, output_dir: &str) -> io::Result<()> { | ||
let tar_gz = File::open(tar_file)?; | ||
let mut archive = tar::Archive::new(tar_gz); | ||
|
||
let mut final_output_dir = output_dir.to_string(); | ||
if Path::new(output_dir).exists() { | ||
final_output_dir = format!("{}-{}", output_dir, uuid::Uuid::new_v4()); | ||
} | ||
|
||
std::fs::create_dir_all(&final_output_dir)?; | ||
archive.unpack(&final_output_dir)?; | ||
Ok(()) | ||
} | ||
|
||
pub fn compress_tar_file( | ||
tar_file: &str, | ||
compressed_file: &str, | ||
) -> io::Result<()> { | ||
let tar = File::open(tar_file)?; | ||
let compressed = File::create(compressed_file)?; | ||
let mut tar_reader = tar; | ||
let mut compressed_writer = compressed; | ||
let compressed_data = encode_all(&mut tar_reader, 0)?; | ||
compressed_writer.write_all(&compressed_data)?; | ||
Ok(()) | ||
} | ||
|
||
pub fn decompress_file( | ||
compressed_file: &str, | ||
output_file: &str, | ||
) -> io::Result<()> { | ||
let compressed = File::open(compressed_file)?; | ||
let decompressed_data = decode_all(compressed)?; | ||
let mut decompressed = File::create(output_file)?; | ||
decompressed.write_all(&decompressed_data)?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use aes_gcm::{ | ||
aead::{Aead, KeyInit}, | ||
Aes256Gcm, Key, Nonce, | ||
}; | ||
use hmac::digest::{generic_array::GenericArray, typenum}; | ||
use rand::RngCore; | ||
use std::fs::File; | ||
use std::io::{self, Write}; | ||
|
||
const SALT_LEN: usize = 16; | ||
const NONCE_LEN: usize = 12; | ||
|
||
fn generate_salt() -> [u8; SALT_LEN] { | ||
let mut salt = [0u8; SALT_LEN]; | ||
rand::thread_rng().fill_bytes(&mut salt); | ||
salt | ||
} | ||
|
||
fn generate_nonce() -> Nonce<typenum::U12> { | ||
let mut nonce = [0u8; NONCE_LEN]; | ||
rand::thread_rng().fill_bytes(&mut nonce); | ||
*Nonce::from_slice(&nonce) | ||
} | ||
|
||
fn derive_key_from_password_argon2(password: &str, salt: &[u8]) -> [u8; 32] { | ||
use argon2::{self, password_hash::SaltString, Argon2, PasswordHasher}; | ||
|
||
let argon2 = Argon2::default(); | ||
let salt = SaltString::encode_b64(salt).unwrap(); | ||
let password_hash = argon2.hash_password(password.as_bytes(), &salt).unwrap(); | ||
let key = password_hash.hash.unwrap(); | ||
let mut key_bytes = [0u8; 32]; | ||
key_bytes.copy_from_slice(key.as_bytes()); | ||
key_bytes | ||
} | ||
|
||
pub fn encrypt_file( | ||
input_file: &str, | ||
output_file: &str, | ||
password: &str, | ||
) -> io::Result<()> { | ||
let salt = generate_salt(); | ||
let key = derive_key_from_password_argon2(password, &salt); | ||
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key)); | ||
let nonce = generate_nonce(); | ||
|
||
let file_content = std::fs::read(input_file)?; | ||
let ciphertext = cipher | ||
.encrypt(&nonce, file_content.as_ref()) | ||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Encryption failure"))?; | ||
|
||
let mut output = File::create(output_file)?; | ||
output.write_all(&salt)?; // Prepend salt | ||
output.write_all(nonce.as_slice())?; // Prepend nonce | ||
output.write_all(&ciphertext)?; | ||
Ok(()) | ||
} | ||
|
||
pub fn decrypt_file( | ||
input_file: &str, | ||
output_file: &str, | ||
password: &str, | ||
) -> io::Result<()> { | ||
let file_content = std::fs::read(input_file)?; | ||
let (salt_and_nonce, ciphertext) = | ||
file_content.split_at(SALT_LEN + NONCE_LEN); // Extract salt and nonce | ||
let (salt, nonce) = salt_and_nonce.split_at(SALT_LEN); // Extract salt | ||
|
||
let key = derive_key_from_password_argon2(password, salt); | ||
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key)); | ||
|
||
let nonce = GenericArray::from_slice(nonce); | ||
let plaintext = match cipher.decrypt(nonce, ciphertext) { | ||
Ok(pt) => pt, | ||
Err(_) => { | ||
return Err(io::Error::new(io::ErrorKind::Other, "Decryption failure")) | ||
} | ||
}; | ||
|
||
let mut output = File::create(output_file)?; | ||
output.write_all(&plaintext)?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub mod cli; | ||
pub mod compression; | ||
pub mod encryption; | ||
pub mod utils; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
use i6_pack::cli; | ||
|
||
use clap::{Arg, Command as ClapCommand}; | ||
use std::io; | ||
|
||
fn main() -> io::Result<()> { | ||
let matches = ClapCommand::new("i6-pack") | ||
.version("0.0.1") | ||
.author("kruserr") | ||
.about( | ||
"Compress and encrypt a folder, or decrypt and decompress an archive", | ||
) | ||
.arg( | ||
Arg::new("action") | ||
.help("Action to perform: pack or unpack") | ||
.required(true) | ||
.index(1), | ||
) | ||
.arg( | ||
Arg::new("target") | ||
.help("Folder to compress and encrypt, or to extract to") | ||
.required(true) | ||
.index(2), | ||
) | ||
.arg( | ||
Arg::new("password") | ||
.help("Password for encryption/decryption") | ||
.required(true) | ||
.index(3), | ||
) | ||
.get_matches(); | ||
|
||
let action = matches.get_one::<String>("action").unwrap(); | ||
let target = matches.get_one::<String>("target").unwrap(); | ||
let password = matches.get_one::<String>("password").unwrap(); | ||
|
||
cli::run(action, target, password) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
use std::io; | ||
use std::path::{Path, PathBuf}; | ||
|
||
pub fn remove_extension(filename: &str, extension: &str) -> String { | ||
filename.strip_suffix(extension).unwrap_or(filename).to_owned() | ||
} | ||
|
||
pub fn validate_path(path: &str) -> io::Result<PathBuf> { | ||
let path = Path::new(path); | ||
if path.exists() { | ||
Ok(path.to_path_buf()) | ||
} else { | ||
Err(io::Error::new(io::ErrorKind::NotFound, "Path does not exist")) | ||
} | ||
} | ||
|
||
pub fn sanitize_output_path(output_path: &str) -> io::Result<PathBuf> { | ||
let path = Path::new(output_path); | ||
if path.is_absolute() | ||
&& !path | ||
.components() | ||
.any(|comp| matches!(comp, std::path::Component::ParentDir)) | ||
{ | ||
Ok(path.to_path_buf()) | ||
} else { | ||
Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid output path")) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
[package] | ||
name = "i6" | ||
version = "0.1.10" # prepare_release.sh | ||
edition = "2021" | ||
default-run = "i6" | ||
license = "AGPL-3.0" | ||
authors = ["kruserr"] | ||
readme = "README.md" | ||
repository = "https://github.com/kruserr/i6" | ||
description = "A collection of tools" | ||
keywords = ["cli", "terminal", "utility", "tool", "command"] | ||
categories = ["command-line-interface", "command-line-utilities", "development-tools"] | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[dependencies] | ||
clap = "3" | ||
|
||
# for http and https commands | ||
tokio = { version = "1", features = ["full"] } | ||
# warp = "0.3" | ||
tracing-subscriber = "0.3" | ||
# for https command | ||
warp = { version = "0.3", features = ["default", "tls"] } | ||
openssl = "0.10" | ||
|
||
i6-pack = { version = "0.1", path = "../i6-pack" } | ||
|
||
# for db command | ||
rapiddb-web = "0.1" | ||
|
||
# for reader command | ||
rustic-reader = "0.1" |
File renamed without changes.
File renamed without changes.
Oops, something went wrong.