Skip to content

Replaced the encryption with age #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ sha2 = "0.10"
sys-locale = "0.3.2"
tabled = {version = "0.10", features = ["color"]}
zeroize = "1.5"
age = "0.11"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to remove chacha20poly1305 and sha2 as dependencies

1 change: 1 addition & 0 deletions locales/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ no_dir_setup_for_x = "There's no directory set up for %{x}"
no_x_setup_yet = "No %{x} have been setup yet"
not_a_tuckr_dotfile = "`%{file}` is not a tuckr dotfile."
wrong_password = "Wrong password."
could_not_read_enc = "Could not read the encrypted dotfile at %{path}"
2 changes: 2 additions & 0 deletions src/dotfiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub enum ReturnCode {
EncryptionFailed = 5,
/// Failed to decrypt referenced file
DecryptionFailed = 6,
/// Failed to read encrypted referenced file
EncryptedReadFailed = 7,
}

impl From<ReturnCode> for process::ExitCode {
Expand Down
108 changes: 42 additions & 66 deletions src/secrets.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,57 @@
//! Manages encrypted files
//!
//! Encrypts files into dotfiles/Secrets using the chacha20poly1305 algorithm
//! Encrypts files into dotfiles/Secrets using the age library

use crate::dotfiles::{self, Dotfile, ReturnCode};
use crate::fileops::DirWalk;
use chacha20poly1305::{AeadCore, KeyInit, XChaCha20Poly1305, aead::Aead};
use owo_colors::OwoColorize;
use rand::rngs;
use rust_i18n::t;
use sha2::{Digest, Sha256};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

struct SecretsHandler {
dotfiles_dir: PathBuf,
key: chacha20poly1305::Key,
nonce: chacha20poly1305::XNonce,
}
use age::secrecy::SecretString;

impl SecretsHandler {
fn try_new(profile: Option<String>) -> Result<Self, ExitCode> {
fn get_dotfiles_dir(profile: Option<String>) -> Result<PathBuf, ExitCode> {
let dotfiles_dir = match dotfiles::get_dotfiles_path(profile) {
Ok(path) => path,
Err(e) => {
eprintln!("{e}");
return Err(ReturnCode::CouldntFindDotfiles.into());
}
};
Ok(dotfiles_dir)
}

// makes a hash of the password so that it can fit on the 256 bit buffer used by the
// algorithm
fn read_passphrase()-> Result<SecretString, ExitCode>{
let input_key = rpassword::prompt_password(format!("{}: ", t!("info.password"))).unwrap();
let input_hash = Sha256::digest(input_key);

Ok(SecretsHandler {
dotfiles_dir,
key: input_hash,
nonce: XChaCha20Poly1305::generate_nonce(&mut rngs::OsRng),
})
}

/// takes a path to a file and returns its encrypted content
fn encrypt(&self, dotfile: &Path) -> Result<Vec<u8>, ExitCode> {
let cipher = XChaCha20Poly1305::new(&self.key);
let Ok(dotfile) = fs::read(dotfile) else {
eprintln!(
"{}",
t!("errors.x_doesnt_exist", x = dotfile.display()).red()
);
return Err(ReturnCode::NoSuchFileOrDir.into());
};

match cipher.encrypt(&self.nonce, dotfile.as_slice()) {
Ok(f) => Ok(f),
Err(e) => {
eprintln!("{}", e.red());
Err(ReturnCode::EncryptionFailed.into())
}
}
}
Ok(SecretString::from(input_key))
}
fn encrypt(recipient: &age::scrypt::Recipient, dotfile: &Path) -> Result<Vec<u8>, ExitCode> {
let Ok(dotfile) = fs::read(dotfile) else {
eprintln!(
"{}",
t!("errors.x_doesnt_exist", x = dotfile.display()).red()
);
return Err(ReturnCode::NoSuchFileOrDir.into());
};

/// takes a path to a file and returns its decrypted content
fn decrypt(&self, dotfile: &str) -> Result<Vec<u8>, ExitCode> {
let cipher = XChaCha20Poly1305::new(&self.key);
let dotfile = fs::read(dotfile).expect("Couldn't read dotfile");
age::encrypt(recipient, &dotfile).map_err(|e|{
eprintln!("{}", e.red());
ReturnCode::EncryptionFailed.into()
})
}

// extracts the nonce from the first 24 bytes in the file
let (nonce, contents) = dotfile.split_at(24);
fn decrypt(identity: &age::scrypt::Identity, dotfile: &Path) -> Result<Vec<u8>, ExitCode> {
let Ok(dotfile) = fs::read(dotfile) else {
eprintln!("{}", t!("errors.could_not_read_enc", path = dotfile.display()).red());
return Err(ReturnCode::EncryptedReadFailed.into())
};

match cipher.decrypt(nonce.into(), contents) {
Ok(f) => Ok(f),
Err(_) => {
eprintln!("{}", t!("errors.wrong_password").red());
Err(ReturnCode::DecryptionFailed.into())
}
}
}
age::decrypt(identity, &dotfile).map_err(|_|{
eprintln!("{}", t!("errors.wrong_password").red());
ReturnCode::DecryptionFailed.into()
})
}

/// Encrypts secrets
Expand All @@ -100,9 +75,11 @@ pub fn encrypt_cmd(
}
}

let handler = SecretsHandler::try_new(profile)?;
//let handler = SecretsHandler::try_new(profile)?;
let passphrase = read_passphrase()?;
let recipient = age::scrypt::Recipient::new(passphrase);

let dest_dir = handler.dotfiles_dir.join("Secrets").join(group);
let dest_dir = get_dotfiles_dir(profile)?.join("Secrets").join(group);
if !dest_dir.exists() {
fs::create_dir_all(&dest_dir).unwrap();
}
Expand Down Expand Up @@ -135,10 +112,7 @@ pub fn encrypt_cmd(
tf
};

let mut encrypted = handler.encrypt(dotfile)?;
let mut encrypted_file = handler.nonce.to_vec();
// appends a 24 byte nonce to the beginning of the file
encrypted_file.append(&mut encrypted);
let encrypted_file = encrypt(&recipient, dotfile)?;

// makes sure all parent directories of the dotfile are created
fs::create_dir_all(dest_dir.join(dir_path)).unwrap();
Expand Down Expand Up @@ -175,7 +149,9 @@ pub fn decrypt_cmd(
groups: &[String],
exclude: &[String],
) -> Result<(), ExitCode> {
let handler = SecretsHandler::try_new(profile.clone())?;
let passphrase = read_passphrase()?;
let identity = age::scrypt::Identity::new(passphrase);
let dotfiles_dir = get_dotfiles_dir(profile.clone())?;

if let Some(invalid_groups) =
dotfiles::check_invalid_groups(profile, dotfiles::DotfileType::Secrets, groups)
Expand All @@ -199,7 +175,7 @@ pub fn decrypt_cmd(
return Ok(());
}

let group_dir = handler.dotfiles_dir.join("Secrets").join(&group.group_path);
let group_dir = dotfiles_dir.join("Secrets").join(&group.group_path);
for secret in DirWalk::new(&group_dir) {
if secret.is_dir() {
continue;
Expand All @@ -221,15 +197,15 @@ pub fn decrypt_cmd(
let decrypted_parent_dir = decrypted_dest.parent().unwrap();
fs::create_dir_all(decrypted_parent_dir).unwrap();

let decrypted = handler.decrypt(secret.to_str().unwrap())?;
let decrypted = decrypt(&identity, &secret)?;
fs::write(decrypted_dest, decrypted).unwrap();
}

Ok(())
};

if groups.contains(&"*".to_string()) {
let groups_dir = handler.dotfiles_dir.join("Secrets");
let groups_dir = dotfiles_dir.join("Secrets");
for group in fs::read_dir(groups_dir).unwrap() {
let Ok(group) = Dotfile::try_from(group.unwrap().path()) else {
eprintln!("{}", t!("errors.got_invalid_group").red());
Expand All @@ -242,7 +218,7 @@ pub fn decrypt_cmd(
}

for group in groups {
let group = handler.dotfiles_dir.join("Secrets").join(group);
let group = dotfiles_dir.join("Secrets").join(group);
let Ok(group) = Dotfile::try_from(group) else {
eprintln!("{}", t!("errors.got_invalid_group").red());
return Err(ExitCode::FAILURE);
Expand Down