Skip to content

Commit

Permalink
support partial decrypt
Browse files Browse the repository at this point in the history
  • Loading branch information
lxl66566 committed May 19, 2024
1 parent 1797803 commit cd57d3a
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = "Encrypt/decrypt files in git repo using one password"
homepage = "https://github.com/lxl66566/git-simple-encrypt"
repository = "https://github.com/lxl66566/git-simple-encrypt"
license = "MIT"
version = "1.1.1"
version = "1.2.0"
edition = "2021"
readme = "./README.md"
categories = ["cryptography"]
Expand Down
12 changes: 12 additions & 0 deletions docs/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
```sh
cargo +nightly install git-simple-encrypt
```
[cargo-binstall](https://github.com/cargo-bins/cargo-binstall)
```sh
cargo binstall git-simple-encrypt
```

## 使用

Expand All @@ -37,8 +41,11 @@ git-se add file.txt # 将 `file.txt` 添加到加密列表
git-se add mydir # 将 `mydir` 添加到加密列表
git-se e # 加密当前仓库所有列表内的文件
git-se d # 解密...
git-se d 'src/*' # 部分解密
```

`git-se -h``git-se [subcommand] -h` 查看更多帮助。

## 注意事项

- 加密时会自动执行 `git add -A`,请确保已妥善处理 `.gitignore`
Expand All @@ -56,3 +63,8 @@ graph TD;

- 如果 zstd 压缩后具有反效果,则跳过压缩。
- 解密时对所有 `.enc`, `.zst.enc` 进行解密。

## TODO

- [ ] zstd effect checking
- [x] partial decrypt
14 changes: 13 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ There are several different ways to install it, you can choose **any** of them.
```sh
cargo +nightly install git-simple-encrypt
```
or [cargo-binstall](https://github.com/cargo-bins/cargo-binstall):
```sh
cargo binstall git-simple-encrypt
```

## Usage

Expand All @@ -36,9 +40,12 @@ git-se set key 123456 # Set the password to `123456`.
git-se add file.txt # Add `file.txt` to the need-to-be-encrypted list.
git-se add mydir # Add `mydir` to the need-to-be-encrypted list.
git-se e # Encrypt files in list in the current repository.
git-se d # Decrypt...
git-se d # Decrypt all files with extension `.enc`, `.zst.enc`.
git-se d 'src/*' # Decrypt all encrypted files in `src` folder.
```

Type `git-se -h` and `git-se [subcommand] -h` to get more information.

## Caution

- `git add -A` is automatically executed when encrypting, so make sure that `.gitignore` is handled properly.
Expand All @@ -56,3 +63,8 @@ graph TD;

- If zstd compression has the opposite effect, skip compression.
- Decrypt all files with extension `.enc`, `.zst.enc`.

## TODO

- [ ] zstd effect checking
- [x] partial decrypt
11 changes: 9 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ git-se set key 123456 # set password as `123456`
git-se add file.txt # mark `file.txt` as need-to-be-crypted
git-se e # encrypt current repo with all marked files
git-se d # decrypt current repo
git-se d 'src/*' # decrypt all encrypted files in `src` folder
"#)]
#[clap(args_conflicts_with_subcommands = true)]
pub struct Cli {
Expand Down Expand Up @@ -40,9 +41,15 @@ pub enum SubCommand {
Encrypt,
/// Decrypt all files with crypt attr and `.enc` extension.
#[clap(alias("d"))]
Decrypt,
Decrypt {
/// The files or folders to be decrypted, use wildcard matches.
path: Option<String>,
},
/// Mark files or folders as need-to-be-crypted.
Add { path: Vec<String> },
Add {
#[clap(required = true)]
paths: Vec<String>,
},
/// Set key or other config items.
Set { field: SetField, value: String },
}
Expand Down
32 changes: 30 additions & 2 deletions src/crypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use aes_gcm_siv::{
};
use anyhow::{anyhow, Context, Result};
use colored::Colorize;
use die_exit::die;
use die_exit::{die, DieWith};
use glob::Pattern;
use log::{debug, info};
use tap::Tap;

Expand Down Expand Up @@ -203,13 +204,40 @@ pub async fn encrypt_repo(repo: &'static Repo) -> anyhow::Result<()> {
Ok(())
}

pub async fn decrypt_repo(repo: &'static Repo) -> anyhow::Result<()> {
pub async fn decrypt_repo(repo: &'static Repo, path: &Option<String>) -> anyhow::Result<()> {
assert!(!repo.get_key().is_empty(), "Key must not be empty");
let dot_pattern = String::from("*.") + ENCRYPTED_EXTENSION;

// partial decrypt filter
let pattern: Option<Pattern> = path.as_ref().map(|x| {
glob::Pattern::new(
repo.path
.join(Path::new(x.as_str()).patch())
.to_string_lossy()
.as_ref()
.tap(|x| println!("Decrypting with pattern: {}", x.green())),
)
.die_with(|e| format!("Invalid pattern: {e}"))
});
let decrypt_path_filter: Box<dyn Fn(&PathBuf) -> bool> = if path.is_some() {
Box::new(|path: &PathBuf| -> bool {
// SAFETY: pattern is always Some in this case
unsafe {
pattern
.as_ref()
.unwrap_unchecked()
.matches(path.to_string_lossy().as_ref())
}
})
} else {
Box::new(|_: &PathBuf| -> bool { true })
};

let decrypt_futures = repo
.ls_files_absolute_with_given_patterns(&[dot_pattern.as_str()])?
.into_iter()
.filter(|x| x.is_file())
.filter(decrypt_path_filter)
.map(|f| decrypt_file(f, repo))
.map(tokio::task::spawn)
.collect::<Vec<_>>();
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ pub async fn run(cli: &Cli) -> Result<()> {
let repo = Box::leak(Box::new(repo));
match &cli.command {
SubCommand::Encrypt => encrypt_repo(repo).await?,
SubCommand::Decrypt => decrypt_repo(repo).await?,
SubCommand::Add { path } => repo
SubCommand::Decrypt { path } => decrypt_repo(repo, path).await?,
SubCommand::Add { paths } => repo
.conf
.add_to_crypt_list(&path.iter().map(|s| s.as_ref()).collect::<Vec<_>>())?,
.add_to_crypt_list(&paths.iter().map(|s| s.as_ref()).collect::<Vec<_>>())?,
SubCommand::Set { field, value } => field.set(repo, value)?,
}
Ok(())
Expand Down
80 changes: 74 additions & 6 deletions tests/intergration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async fn test() -> anyhow::Result<()> {

// Add file
run!(SubCommand::Add {
path: ["t1.txt", "t2.txt", "dir"]
paths: ["t1.txt", "t2.txt", "dir"]
.map(ToString::to_string)
.to_vec(),
});
Expand All @@ -73,7 +73,7 @@ async fn test() -> anyhow::Result<()> {
assert!(!temp_dir.join("dir/t4.txt").exists());

// Decrypt
run!(SubCommand::Decrypt);
run!(SubCommand::Decrypt { path: None });
println!("{}", "After Decrypt".green());

// Test
Expand Down Expand Up @@ -134,7 +134,7 @@ async fn test_reencrypt() -> anyhow::Result<()> {

// Add file
run!(SubCommand::Add {
path: ["t1.txt", "dir"].map(ToString::to_string).to_vec(),
paths: ["t1.txt", "dir"].map(ToString::to_string).to_vec(),
});

// Encrypt multiple times
Expand All @@ -154,7 +154,7 @@ async fn test_reencrypt() -> anyhow::Result<()> {
assert!(!temp_dir.join("dir/t4.txt").exists());

// Decrypt
run!(SubCommand::Decrypt);
run!(SubCommand::Decrypt { path: None });
println!("{}", "After Decrypt".green());

// Test
Expand Down Expand Up @@ -214,13 +214,13 @@ async fn test_many_files() -> anyhow::Result<()> {

// Add file
run!(SubCommand::Add {
path: vec!["dir".into()]
paths: vec!["dir".into()]
});

// Encrypt
run!(SubCommand::Encrypt);
// Decrypt
run!(SubCommand::Decrypt);
run!(SubCommand::Decrypt { path: None });

// Test
for _ in 1..10 {
Expand All @@ -231,3 +231,71 @@ async fn test_many_files() -> anyhow::Result<()> {

Ok(())
}

#[tokio::test]
async fn test_partial_decrypt() -> anyhow::Result<()> {
let _ = env_logger::try_init();
let temp_dir = &TempDir::default();
let exec = |cmd: &str| -> std::io::Result<Output> {
let mut temp = cmd.split_whitespace();
let mut command = Command::new(temp.next().unwrap());
command.args(temp).current_dir(temp_dir).output()
};
macro_rules! run {
($cmd:expr) => {
run(&Cli {
command: $cmd,
repo: temp_dir.to_path_buf(),
})
.await?;
};
}

exec("git init")?;
std::fs::create_dir(temp_dir.join("dir"))?;
std::fs::write(temp_dir.join("t1.txt"), "Hello, world!")?;
std::fs::write(temp_dir.join("dir/t4.txt"), "dir test")?;

// Set key
run!(SubCommand::Set {
field: SetField::key,
value: "123".to_owned(),
});

// Add file
run!(SubCommand::Add {
paths: ["t1.txt", "dir"].map(ToString::to_string).to_vec(),
});

// Encrypt
run!(SubCommand::Encrypt);

// Partial decrypt
run!(SubCommand::Decrypt {
path: Some("dir/**".into())
});

// Test
for entry in temp_dir.read_dir()? {
println!("{:?}", entry?);
}
assert!(temp_dir.join("t1.txt.enc").exists());
assert!(temp_dir.join("dir/t4.txt").exists());

// Reencrypt
run!(SubCommand::Encrypt);

// Partial decrypt
run!(SubCommand::Decrypt {
path: Some("t1.txt.enc".into())
});

// Test
for entry in temp_dir.read_dir()? {
println!("{:?}", entry?);
}
assert!(temp_dir.join("t1.txt").exists());
assert!(temp_dir.join("dir/t4.txt.enc").exists());

Ok(())
}

0 comments on commit cd57d3a

Please sign in to comment.