diff --git a/.gitignore b/.gitignore index 077b91f..93aa485 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ *.py enc mount -.cargo \ No newline at end of file +.cargo + +test.ps1 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 85b18bf..e67349e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,9 +25,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", "cipher 0.4.3", @@ -50,18 +50,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "atty" @@ -82,9 +82,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.22" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", @@ -214,11 +214,21 @@ dependencies = [ "cipher 0.3.0", ] +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -236,9 +246,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -274,9 +284,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -340,9 +350,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -366,9 +376,9 @@ checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "libc" -version = "0.2.134" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "log" @@ -385,11 +395,23 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -399,9 +421,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_str_bytes" -version = "6.3.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "page_size" @@ -471,9 +493,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -498,9 +520,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -509,15 +531,26 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rpassword" -version = "7.0.0" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +dependencies = [ + "libc", + "rtoolbox", + "winapi", +] + +[[package]] +name = "rtoolbox" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" dependencies = [ "libc", "winapi", @@ -553,7 +586,7 @@ dependencies = [ name = "rustcryptfs-lib" version = "0.1.0" dependencies = [ - "aes 0.8.1", + "aes 0.8.2", "aes-gcm", "base64", "cipher 0.4.3", @@ -571,6 +604,19 @@ name = "rustcryptfs-mount" version = "0.1.0" dependencies = [ "rustcryptfs-fuse", + "rustcryptfs-projfs", +] + +[[package]] +name = "rustcryptfs-projfs" +version = "0.1.0" +dependencies = [ + "ctrlc", + "libc", + "log", + "rustcryptfs-lib", + "thiserror", + "windows-sys", ] [[package]] @@ -603,18 +649,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" dependencies = [ "proc-macro2", "quote", @@ -623,9 +669,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -663,27 +709,15 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.102" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -695,9 +729,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" @@ -721,9 +755,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-ident" @@ -731,12 +765,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "universal-hash" version = "0.4.1" @@ -800,6 +828,72 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "zerocopy" version = "0.6.1" @@ -812,11 +906,11 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f" +checksum = "6505e6815af7de1746a08f69c69606bb45695a17149517680f3b2149713b19a3" dependencies = [ "proc-macro2", + "quote", "syn", - "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index 2fd6a7d..bae08e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [workspace] +resolver = "2" members = [ "rustcryptfs", "rustcryptfs-lib", "rustcryptfs-fuse", - "rustcryptfs-mount" + "rustcryptfs-mount", + "rustcryptfs-projfs" ] [profile.release] diff --git a/README.md b/README.md index f9f1b75..af13151 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ An implementation of [gocryptfs](https://github.com/rfjakob/gocryptfs) in Rust. - [x] Linux (via FUSE) - [x] read - [ ] write -- [ ] Windows - - [ ] read +- [x] Windows + - [x] read - [ ] write ## Features diff --git a/rustcryptfs-fuse/src/error.rs b/rustcryptfs-fuse/src/error.rs index 13b06cf..c754927 100644 --- a/rustcryptfs-fuse/src/error.rs +++ b/rustcryptfs-fuse/src/error.rs @@ -15,7 +15,6 @@ pub enum Error { RustCryptFsFilenameError(#[from] FilenameCipherError), } - pub(crate) trait ErrorExt { fn to_raw_code(&self) -> i32; } @@ -30,4 +29,4 @@ impl ErrorExt for rustcryptfs_lib::error::Error { rustcryptfs_lib::error::Error::IoError(e) => e.raw_os_error().unwrap(), } } -} \ No newline at end of file +} diff --git a/rustcryptfs-lib/src/config/mod.rs b/rustcryptfs-lib/src/config/mod.rs index 656990b..a970c4c 100644 --- a/rustcryptfs-lib/src/config/mod.rs +++ b/rustcryptfs-lib/src/config/mod.rs @@ -71,7 +71,7 @@ impl CryptConf { /// See gocryptfs documentation about [master key](https://nuetzlich.net/gocryptfs/forward_mode_crypto/#master-key-storage). /// /// ![TODO NAME THIS IMAGE](https://nuetzlich.net/gocryptfs/img/master-key.svg) - /// + /// /// # Errors /// Return an error when the master key don't have the required size or if the decrypting failed. pub fn get_master_key(&self, password: &[u8]) -> Result<[u8; 32], ConfigError> { diff --git a/rustcryptfs-lib/src/content/mod.rs b/rustcryptfs-lib/src/content/mod.rs index fb03c75..1c0f14b 100644 --- a/rustcryptfs-lib/src/content/mod.rs +++ b/rustcryptfs-lib/src/content/mod.rs @@ -19,7 +19,7 @@ pub struct ContentEnc { impl ContentEnc { /// Init a new `ContentEnc` from the master key and the iv len. - /// + /// /// # Errors /// Return an error if the filename key cannot be derived from the `master_key`. pub fn new(master_key: &[u8], iv_len: u8) -> Result { @@ -35,7 +35,7 @@ impl ContentEnc { /// Decrypt a encrypted block of len (`iv_len` + `decrypted_block_size` + `iv_len`), with the block number and the file id. /// The content of block is replaced with the plain text, in form of iv + plaintext + tag. - /// + /// /// # Errors /// Return an error if `block` is too short, or if the nonce is full of 0, or if the decrypting failed. pub fn decrypt_block<'a>( diff --git a/rustcryptfs-mount/Cargo.toml b/rustcryptfs-mount/Cargo.toml index 00f85d8..5e2ff81 100644 --- a/rustcryptfs-mount/Cargo.toml +++ b/rustcryptfs-mount/Cargo.toml @@ -11,3 +11,6 @@ repository = "https://github.com/oupson/rustcryptfs/" [target.'cfg(target_os = "linux")'.dependencies] rustcryptfs-fuse = { path = "../rustcryptfs-fuse" } + +[target.'cfg(target_os = "windows")'.dependencies] +rustcryptfs-projfs = { path = "../rustcryptfs-projfs" } \ No newline at end of file diff --git a/rustcryptfs-mount/src/lib.rs b/rustcryptfs-mount/src/lib.rs index 107aa9d..0b933c0 100644 --- a/rustcryptfs-mount/src/lib.rs +++ b/rustcryptfs-mount/src/lib.rs @@ -12,3 +12,16 @@ where fs.mount(mount_point)?; Ok(()) } + +#[cfg(target_os = "windows")] +pub fn mount

(path: P, mount_point: P, password: &str) -> rustcryptfs_projfs::error::Result<()> +where + P: AsRef, +{ + use rustcryptfs_projfs::EncryptedFs; + + let fs = EncryptedFs::new(path, password)?; + + fs.mount(mount_point)?; + Ok(()) +} diff --git a/rustcryptfs-projfs/Cargo.toml b/rustcryptfs-projfs/Cargo.toml new file mode 100644 index 0000000..38a2081 --- /dev/null +++ b/rustcryptfs-projfs/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rustcryptfs-projfs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4" +rustcryptfs-lib = { path = "../rustcryptfs-lib" } +thiserror = "1.0" +libc = "0.2" +ctrlc = "3.2" + +[dependencies.windows-sys] +version = "0.52.0" +features = [ + "Win32_Storage_ProjectedFileSystem", + "Win32_Foundation", + "Win32_System_Com", + "Win32_System_Diagnostics_Debug" +] \ No newline at end of file diff --git a/rustcryptfs-projfs/src/error.rs b/rustcryptfs-projfs/src/error.rs new file mode 100644 index 0000000..077aee3 --- /dev/null +++ b/rustcryptfs-projfs/src/error.rs @@ -0,0 +1,112 @@ +use std::{ + ffi::CStr, + fmt::{Debug, Display}, + slice, +}; + +use rustcryptfs_lib::filename::FilenameCipherError; +use thiserror::Error; +use windows_sys::Win32::System::Diagnostics::Debug::{ + FormatMessageA, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, +}; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + WindowsError(#[from] WinError), + + #[error(transparent)] + RustCryptFsError(#[from] rustcryptfs_lib::error::Error), + + #[error(transparent)] + RustCryptFsFilenameError(#[from] FilenameCipherError), +} + +#[repr(transparent)] +pub struct WinError(pub i32); + +impl Display for WinError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buffer = std::ptr::null_mut(); + let size = unsafe { + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + std::ptr::null(), + self.0 as u32, + 0, + &mut buffer as *mut *mut i8 as *mut _, + 0, + std::ptr::null(), + ) + }; + + let buffer = unsafe { + CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts( + buffer as *mut u8, + (size + 1) as usize, + )) + }; + + write!( + f, + "Windows Error : 0x{:08X} :{}", + self.0, + buffer.to_string_lossy() + ) + } +} + +impl Debug for WinError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buffer = std::ptr::null_mut(); + let size = unsafe { + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + std::ptr::null(), + self.0 as u32, + 0, + &mut buffer as *mut *mut i8 as *mut _, + 0, + std::ptr::null(), + ) + }; + + let buffer = unsafe { + CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts( + buffer as *mut u8, + (size + 1) as usize, + )) + }; + + f.debug_struct("WinError") + .field("error_code", &format!("0x{:08X}", self.0)) + .field("msg", &buffer.to_string_lossy()) + .finish() + } +} + +impl std::error::Error for WinError {} + +pub trait ToWinResult: Sized { + fn to_win_result(self) -> std::result::Result; +} + +impl ToWinResult for i32 { + fn to_win_result(self) -> std::result::Result { + if self < 0 { + Err(crate::error::WinError(self)) + } else { + Ok(self) + } + } +} diff --git a/rustcryptfs-projfs/src/lib.rs b/rustcryptfs-projfs/src/lib.rs new file mode 100644 index 0000000..6e2e981 --- /dev/null +++ b/rustcryptfs-projfs/src/lib.rs @@ -0,0 +1,263 @@ +use std::{ + collections::HashMap, + fs::File, + hash::{Hash, Hasher}, + io::Read, + mem::MaybeUninit, + os::{raw::c_void, windows::prelude::OsStrExt}, + path::{Path, PathBuf}, + sync::{atomic::AtomicBool, mpsc::channel}, +}; + +use log::{info, warn}; +use rustcryptfs_lib::GocryptFs; + +use error::Result; +use rustcryptfs_lib::filename::EncodedFilename; +use windows_sys::Win32::Storage::ProjectedFileSystem::{ + PrjDeleteFile, PrjGetOnDiskFileState, PRJ_FILE_STATE, PRJ_PLACEHOLDER_VERSION_INFO, + PRJ_UPDATE_ALLOW_DIRTY_DATA, PRJ_UPDATE_ALLOW_DIRTY_METADATA, PRJ_UPDATE_ALLOW_READ_ONLY, + PRJ_UPDATE_ALLOW_TOMBSTONE, +}; + +use windows_sys::{ + core::GUID, + Win32::{ + Storage::ProjectedFileSystem::{ + PrjMarkDirectoryAsPlaceholder, PrjStartVirtualizing, PrjStopVirtualizing, + PRJ_CALLBACKS, PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, + }, + System::Com::CoCreateGuid, + }, +}; + +use crate::error::ToWinResult; + +pub mod error; +mod projfs; +pub(crate) mod write_buffer; + +#[repr(transparent)] +struct WinGuid(GUID); + +impl PartialEq for WinGuid { + fn eq(&self, other: &Self) -> bool { + self.0.data1 == other.0.data1 + && self.0.data2 == other.0.data2 + && self.0.data3 == other.0.data3 + && self.0.data4 == other.0.data4 + } +} + +impl Eq for WinGuid {} + +impl Hash for WinGuid { + fn hash(&self, state: &mut H) { + self.0.data1.hash(state); + self.0.data2.hash(state); + self.0.data3.hash(state); + self.0.data4.hash(state); + } +} + +pub struct EncryptedFs { + fs: GocryptFs, + filename_map: HashMap, + enum_map: HashMap, + base_path: PathBuf, + is_stopping: AtomicBool, +} + +impl EncryptedFs { + pub(crate) fn retrieve_filename<'p>(&'p self, filename: &'p Path) -> Option<&'p Path> { + if filename.as_os_str().len() == 0 { + Some(filename) + } else { + self.filename_map.get(filename).map(|p| p.as_path()) + } + } + + pub(crate) fn insert_filename(&mut self, filename: PathBuf, real_path: PathBuf) { + self.filename_map.insert(filename, real_path); + } + + pub(crate) fn get_path(&mut self, filename: PathBuf) -> PathBuf { + let path = match self.retrieve_filename(&filename) { + Some(p) => p.to_path_buf(), + None => { + let parent = filename.parent().unwrap(); + let name = filename.file_name().unwrap(); + let real_parent: &Path = self.retrieve_filename(&parent).unwrap(); + + let mut iv = [0u8; 16]; + + { + let mut iv_file = + File::open(self.base_path.join(real_parent).join("gocryptfs.diriv")) + .unwrap(); + iv_file.read_exact(&mut iv).unwrap(); + } + + let d = self.fs.filename_decoder().get_cipher_for_dir(&iv); + + let encrypted_name = d.encrypt_filename(&name.to_string_lossy()).unwrap(); + + let encoded_name = match &encrypted_name { + EncodedFilename::ShortFilename(s) => s, + EncodedFilename::LongFilename(l) => l.filename(), + }; + + let real_path = real_parent.join(encoded_name); + + self.insert_filename(filename, real_path.clone()); + + real_path + } + }; + + self.base_path.join(path) + } +} + +impl EncryptedFs { + pub fn new

(path: P, password: &str) -> Result + where + P: AsRef, + { + let path = path.as_ref(); + + info!("Opening dir ..."); + let fs = GocryptFs::open(path, password)?; + + println!("Filesystem mounted and ready."); + + Ok(Self { + fs, + filename_map: Default::default(), + enum_map: Default::default(), + base_path: path.to_owned(), + is_stopping: AtomicBool::new(false), + }) + } + + pub fn mount

(self, mountpoint: P) -> crate::Result<()> + where + P: AsRef, + { + let mountpoint = mountpoint.as_ref(); + unsafe { + let mut instance_id: GUID = MaybeUninit::zeroed().assume_init(); + CoCreateGuid(&mut instance_id).to_win_result()?; + + let mut root_name: Vec = mountpoint.as_os_str().encode_wide().collect(); + root_name.push(0); + + let info: PRJ_PLACEHOLDER_VERSION_INFO = MaybeUninit::zeroed().assume_init(); + + let ptr = root_name.as_ptr(); + PrjMarkDirectoryAsPlaceholder(ptr, std::ptr::null(), &info, &instance_id) + .to_win_result()?; + + let mut callback_table: PRJ_CALLBACKS = MaybeUninit::zeroed().assume_init(); + + callback_table.StartDirectoryEnumerationCallback = Some(projfs::start_enum_callback); + callback_table.EndDirectoryEnumerationCallback = Some(projfs::end_enum_callback); + callback_table.GetDirectoryEnumerationCallback = Some(projfs::get_enum_callback); + callback_table.GetPlaceholderInfoCallback = Some(projfs::get_placeholder_info_callback); + callback_table.GetFileDataCallback = Some(projfs::get_file_data_callback); + + let this = Box::leak(Box::new(self)) as *mut EncryptedFs; + + let mut instance_handle: PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT = + MaybeUninit::zeroed().assume_init(); + PrjStartVirtualizing( + ptr, + &callback_table, + this as *const c_void, + std::ptr::null(), + &mut instance_handle, + ) + .to_win_result()?; + + let (tx, rx) = channel(); + + ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) + .expect("Error setting Ctrl-C handler"); + + rx.recv().expect("Could not receive from channel."); + + println!("Exiting ..."); + + (*this) + .is_stopping + .store(true, std::sync::atomic::Ordering::Relaxed); + + delete_recursively(instance_handle, &mountpoint, &mountpoint)?; + + PrjStopVirtualizing(instance_handle); + + drop(Box::from_raw(this)); + } + Ok(()) + } +} + +unsafe fn delete_recursively( + instance: PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, + root_path: &Path, + path: &Path, +) -> Result<()> { + let mut iter = std::fs::read_dir(path)?.filter_map(|r| r.ok()); + while let Some(e) = iter.next() { + let mut file_state: PRJ_FILE_STATE = std::mem::zeroed(); + let wide_name = e + .path() + .as_os_str() + .encode_wide() + .chain(std::iter::once(0)) + .collect::>(); + + if PrjGetOnDiskFileState(wide_name.as_ptr(), &mut file_state) + .to_win_result() + .is_ok() + { + let path = e.path(); + let delete_result = if e.file_type()?.is_dir() { + delete_recursively(instance, root_path, &path)?; + delete_file_from_projection(instance, path.strip_prefix(root_path).unwrap()) + } else { + delete_file_from_projection(instance, path.strip_prefix(root_path).unwrap()) + }; + + if let Err(e) = delete_result { + warn!("failed to delete a file : {}", e); + } + } + } + + Ok(()) +} + +fn delete_file_from_projection( + instance: PRJ_NAMESPACE_VIRTUALIZATION_CONTEXT, + filename: &Path, +) -> Result<()> { + let filename = filename + .as_os_str() + .encode_wide() + .chain(std::iter::once(0)) + .collect::>(); + unsafe { + PrjDeleteFile( + instance, + filename.as_ptr(), + PRJ_UPDATE_ALLOW_DIRTY_DATA + | PRJ_UPDATE_ALLOW_DIRTY_METADATA + | PRJ_UPDATE_ALLOW_READ_ONLY + | PRJ_UPDATE_ALLOW_TOMBSTONE, + std::ptr::null_mut(), + ) + .to_win_result()?; + } + Ok(()) +} diff --git a/rustcryptfs-projfs/src/projfs.rs b/rustcryptfs-projfs/src/projfs.rs new file mode 100644 index 0000000..6f96390 --- /dev/null +++ b/rustcryptfs-projfs/src/projfs.rs @@ -0,0 +1,345 @@ +use std::{ + cmp::Ordering, + ffi::{c_ulong, OsString}, + fs::{DirEntry, File}, + io::{Read, Seek, SeekFrom}, + os::windows::{fs::MetadataExt, prelude::OsStringExt}, + path::PathBuf, +}; + +use log::{trace, warn}; +use rustcryptfs_lib::content::ContentEnc; +use windows_sys::{ + core::{GUID, HRESULT, PCWSTR}, + Win32::{ + Foundation::{ERROR_FILE_NOT_FOUND, E_INVALIDARG}, + Storage::ProjectedFileSystem::{ + PrjFileNameCompare, PrjFillDirEntryBuffer, PrjWritePlaceholderInfo, PRJ_CALLBACK_DATA, + PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN, PRJ_DIR_ENTRY_BUFFER_HANDLE, PRJ_FILE_BASIC_INFO, + PRJ_PLACEHOLDER_INFO, + }, + System::Diagnostics::Debug::FACILITY_WIN32, + }, +}; + +// TODO windows::core::HRESULT + +#[inline] +pub fn hresult_from_win32(x: c_ulong) -> HRESULT { + if x as i32 <= 0 { + x as i32 + } else { + ((x & 0x0000FFFF) | ((FACILITY_WIN32 as u32) << 16) | 0x80000000) as i32 + } +} + +use crate::EncryptedFs; + +unsafe fn u16_ptr_to_string(ptr: *const u16) -> OsString { + let len = (0..).take_while(|&i| *ptr.offset(i) != 0).count(); + let slice = std::slice::from_raw_parts(ptr, len); + + OsString::from_wide(slice) +} + +pub(crate) struct DirEnumData { + last_entry: Option<(Vec, PRJ_FILE_BASIC_INFO)>, + entries: Vec<(Vec, DirEntry)>, + iter_index: Option, + search_expression: Option>, +} + +pub(crate) unsafe extern "system" fn start_enum_callback( + callback_data: *const PRJ_CALLBACK_DATA, + enumeration_id: *const GUID, +) -> HRESULT { + trace!("start_enum_callback"); + let callback_data = &*callback_data; + let instance_context = &mut *(callback_data.InstanceContext as *mut EncryptedFs); + let filename = PathBuf::from(u16_ptr_to_string(callback_data.FilePathName)); + + let path = instance_context.get_path(filename); + + let mut iv = [0u8; 16]; + + { + let mut iv_file = File::open(path.join("gocryptfs.diriv")).unwrap(); + iv_file.read_exact(&mut iv).unwrap(); + } + + let mut entries = std::fs::read_dir(path) + .unwrap() + .filter_map(|e| e.ok()) + .filter(|e| e.file_name() != "gocryptfs.conf" && e.file_name() != "gocryptfs.diriv") + .map(|entry| { + ( + instance_context + .fs + .filename_decoder() + .get_cipher_for_dir(&iv) + .decode_filename(&*entry.file_name().to_string_lossy()) + .unwrap() + .encode_utf16() + .chain(std::iter::once(0)) + .collect::>(), + entry, + ) + }) + .collect::>(); + + entries.sort_by(|entry1, entry2| { + let comp = PrjFileNameCompare(entry1.0.as_ptr(), entry2.0.as_ptr()); + if comp < 0 { + Ordering::Less + } else if comp > 0 { + Ordering::Greater + } else { + Ordering::Equal + } + }); + + instance_context.enum_map.insert( + crate::WinGuid(*enumeration_id), + DirEnumData { + last_entry: None, + entries, + iter_index: None, + search_expression: None, + }, + ); + + 0 +} + +pub(crate) unsafe extern "system" fn end_enum_callback( + callback_data: *const PRJ_CALLBACK_DATA, + enumeration_id: *const GUID, +) -> HRESULT { + trace!("end_enum_callback"); + let callback_data = &*callback_data; + let instance_context = &mut *(callback_data.InstanceContext as *mut EncryptedFs); + + instance_context + .enum_map + .remove(&crate::WinGuid(*enumeration_id)); + + 0 +} + +// TODO : Search expression +pub(crate) unsafe extern "system" fn get_enum_callback( + callback_data: *const PRJ_CALLBACK_DATA, + enumeration_id: *const GUID, + search_expression: PCWSTR, + dir_entry_buffer_handle: PRJ_DIR_ENTRY_BUFFER_HANDLE, +) -> ::windows_sys::core::HRESULT { + trace!("get_enum_callback"); + let callback_data = &*callback_data; + let instance_context = &mut *(callback_data.InstanceContext as *mut EncryptedFs); + + if instance_context + .is_stopping + .load(std::sync::atomic::Ordering::Relaxed) + { + return 0; + } + + let data = if let Some(data) = instance_context + .enum_map + .get_mut(&crate::WinGuid(*enumeration_id)) + { + data + } else { + warn!("unknown enumeration"); + return E_INVALIDARG; + }; + + if callback_data.Flags & PRJ_CB_DATA_FLAG_ENUM_RESTART_SCAN == 1 { + data.last_entry = None; + data.iter_index = Some(0); + + if search_expression != std::ptr::null() { + let len = libc::wcslen(search_expression) + 1; + data.search_expression = + Some(std::slice::from_raw_parts(search_expression, len).to_vec()); + } else { + data.search_expression = None; + } + } + + let mut last_index = data.iter_index.unwrap_or(0); + + if let Some((last_filename, last_info)) = std::mem::replace(&mut data.last_entry, None) { + let insert = if let Some(search_expression) = &data.search_expression { + PrjFileNameCompare(last_filename.as_ptr(), search_expression.as_ptr()) != 0 + } else { + false + }; + + if insert { + PrjFillDirEntryBuffer(last_filename.as_ptr(), &last_info, dir_entry_buffer_handle); + } + } + + while last_index < data.entries.len() { + let (filename, entry) = &data.entries[last_index]; + + let insert = if let Some(search_expression) = &data.search_expression { + PrjFileNameCompare(filename.as_ptr(), search_expression.as_ptr()) != 0 + } else { + false + }; + + if !insert { + continue; + } + + let metadata = entry.metadata().unwrap(); + + let infos = PRJ_FILE_BASIC_INFO { + IsDirectory: if metadata.is_dir() { 1 } else { 0 }, + FileSize: if metadata.is_dir() { + 0 + } else { + ContentEnc::get_real_size(metadata.file_size()) as i64 + }, + CreationTime: metadata.creation_time() as i64, + LastAccessTime: metadata.last_access_time() as i64, + LastWriteTime: metadata.last_write_time() as i64, + ChangeTime: metadata.last_write_time() as i64, + FileAttributes: metadata.file_attributes(), + }; + + // TODO check if HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) + if PrjFillDirEntryBuffer(filename.as_ptr(), &infos, dir_entry_buffer_handle) != 0 { + trace!("not enough size in buffer"); + data.last_entry = Some((filename.clone(), infos)); + break; + } + + last_index += 1; + } + + data.iter_index = Some(last_index); + + 0 +} + +pub(crate) unsafe extern "system" fn get_placeholder_info_callback( + callback_data: *const PRJ_CALLBACK_DATA, +) -> ::windows_sys::core::HRESULT { + trace!("get_placeholder_info_callback"); + let callback_data = &*callback_data; + let instance_context = &mut *(callback_data.InstanceContext as *mut EncryptedFs); + let filename = PathBuf::from(u16_ptr_to_string(callback_data.FilePathName)); + + let path = instance_context.get_path(filename); + + match path.metadata() { + Ok(metadata) => { + let mut infos: PRJ_PLACEHOLDER_INFO = std::mem::zeroed(); + infos.FileBasicInfo = PRJ_FILE_BASIC_INFO { + IsDirectory: if metadata.is_dir() { 1 } else { 0 }, + FileSize: if metadata.is_dir() { + 0 + } else { + ContentEnc::get_real_size(metadata.file_size()) as i64 + }, + CreationTime: metadata.creation_time() as i64, + LastAccessTime: metadata.last_access_time() as i64, + LastWriteTime: metadata.last_write_time() as i64, + ChangeTime: metadata.last_write_time() as i64, + FileAttributes: metadata.file_attributes(), + }; + + let hr = PrjWritePlaceholderInfo( + callback_data.NamespaceVirtualizationContext, + callback_data.FilePathName, + &infos, + std::mem::size_of_val(&infos) as u32, + ); + + hr + } + Err(e) => { + log::trace!("{}", e); + + hresult_from_win32(ERROR_FILE_NOT_FOUND) + } + } +} + +pub(crate) unsafe extern "system" fn get_file_data_callback( + callback_data: *const PRJ_CALLBACK_DATA, + byte_offset: u64, + length: u32, +) -> HRESULT { + trace!("get_file_data_callback"); + let callback_data = &*callback_data; + let instance_context = &mut *(callback_data.InstanceContext as *mut EncryptedFs); + let filename = PathBuf::from(u16_ptr_to_string(callback_data.FilePathName)); + + let path = instance_context.get_path(filename.clone()); + + let size = length as usize; + + let mut file = File::open(path).unwrap(); + let decoder = instance_context.fs.content_decoder(); + + let mut buf = [0u8; 18]; + let n = file.read(&mut buf).unwrap(); + let id = if n < 18 { None } else { Some(&buf[2..]) }; + + let mut block_index = byte_offset / 4096; + + let mut rem = size; + + let mut writter = crate::write_buffer::WriteBuffer::new( + callback_data.NamespaceVirtualizationContext, + callback_data.DataStreamId, + byte_offset, + ); + let mut buf = [0u8; 4096 + 32]; + + file.seek(SeekFrom::Start(18 + block_index * (4096 + 32))) + .unwrap(); + + { + let n = file.read(&mut buf).unwrap(); + + let res = decoder + .decrypt_block(&mut buf[..n], block_index, id) + .unwrap(); + + let seek = (byte_offset as u64 - block_index * 4096) as usize; + + writter.append_buf(&res[seek..]); + block_index += 1; + + rem -= res.len() - seek; + } + + while rem > 0 { + let n = file.read(&mut buf).unwrap(); + + if n == 0 { + break; + } + + let res = decoder + .decrypt_block(&mut buf[..n], block_index, id) + .unwrap(); + + let size = res.len().min(rem); + + writter.append_buf(&res[..size]); + + block_index += 1; + + rem -= size; + } + + writter.finish(); + + 0 +} diff --git a/rustcryptfs-projfs/src/write_buffer.rs b/rustcryptfs-projfs/src/write_buffer.rs new file mode 100644 index 0000000..f4c9adf --- /dev/null +++ b/rustcryptfs-projfs/src/write_buffer.rs @@ -0,0 +1,105 @@ +use std::ffi::c_void; + +use windows_sys::{ + core::GUID, + Win32::Storage::ProjectedFileSystem::{ + PrjAllocateAlignedBuffer, PrjFreeAlignedBuffer, PrjGetVirtualizationInstanceInfo, + PrjWriteFileData, PRJ_VIRTUALIZATION_INSTANCE_INFO, + }, +}; + +pub(crate) struct WriteBuffer { + buffer_size: usize, + namespace_virtualization_context: isize, + data_stream_id: GUID, + prjfs_buf: *mut c_void, + file_offset: u64, + offset: usize, +} + +impl WriteBuffer { + pub(crate) fn new( + namespace_virtualization_context: isize, + data_stream_id: GUID, + file_offset: u64, + ) -> Self { + let (size, buf) = unsafe { + let mut info: PRJ_VIRTUALIZATION_INSTANCE_INFO = std::mem::zeroed(); + PrjGetVirtualizationInstanceInfo(namespace_virtualization_context, &mut info); + + let size = (info.WriteAlignment * 100) as usize; + ( + size, + PrjAllocateAlignedBuffer(namespace_virtualization_context, size), + ) + }; + + Self { + buffer_size: size, + namespace_virtualization_context, + data_stream_id, + prjfs_buf: buf, + file_offset, + offset: 0, + } + } + + pub(crate) fn append_buf(&mut self, buf: &[u8]) -> usize { + let mut remaining_buf = buf; + let mut written = 0; + + while !remaining_buf.is_empty() { + let to_copy = (self.buffer_size - self.offset).min(remaining_buf.len()); + + unsafe { + (self.prjfs_buf as *mut u8) + .offset(self.offset as isize) + .copy_from(remaining_buf.as_ptr(), to_copy); + } + + self.offset += to_copy; + + if self.offset == self.buffer_size { + unsafe { + PrjWriteFileData( + self.namespace_virtualization_context, + &self.data_stream_id, + self.prjfs_buf, + self.file_offset, + self.buffer_size as u32, + ) + }; + + self.file_offset += self.buffer_size as u64; + self.offset = 0; + written += self.buffer_size; + }; + + remaining_buf = &remaining_buf[to_copy..]; + } + written + } + + pub(crate) fn finish(&mut self) -> usize { + let written = self.offset; + unsafe { + PrjWriteFileData( + self.namespace_virtualization_context, + &self.data_stream_id, + self.prjfs_buf, + self.file_offset, + self.offset as u32, + ) + }; + self.file_offset += self.offset as u64; + self.offset = 0; + written + } +} + +impl Drop for WriteBuffer { + fn drop(&mut self) { + unsafe { PrjFreeAlignedBuffer(self.prjfs_buf) }; + self.prjfs_buf = std::ptr::null_mut(); + } +} diff --git a/rustcryptfs/src/args.rs b/rustcryptfs/src/args.rs index b21f2b2..45464f1 100644 --- a/rustcryptfs/src/args.rs +++ b/rustcryptfs/src/args.rs @@ -27,29 +27,29 @@ pub(crate) enum Commands { #[derive(Debug, Parser)] pub(crate) struct DecryptCommand { /// The file to decrypt - pub(crate) file_path : String, + pub(crate) file_path: String, /// Path to the gocryptfs directory #[clap(short('g'), long)] - pub(crate) gocryptfs_path : Option, + pub(crate) gocryptfs_path: Option, /// The password #[clap(short, long)] - pub(crate) password : Option + pub(crate) password: Option, } #[derive(Debug, Parser)] pub(crate) struct LsCommand { /// The directory - pub(crate) folder_path : String, + pub(crate) folder_path: String, /// Path to the gocryptfs directory #[clap(short('g'), long)] - pub(crate) gocryptfs_path : Option, + pub(crate) gocryptfs_path: Option, /// The password #[clap(short, long)] - pub(crate) password : Option + pub(crate) password: Option, } #[cfg(feature = "mount")] diff --git a/rustcryptfs/src/main.rs b/rustcryptfs/src/main.rs index edd9845..a22fcca 100644 --- a/rustcryptfs/src/main.rs +++ b/rustcryptfs/src/main.rs @@ -140,5 +140,16 @@ fn mount(mount: &MountCommand) -> anyhow::Result<()> { #[cfg(target_os = "windows")] #[cfg(feature = "mount")] fn mount(mount: &MountCommand) -> anyhow::Result<()> { - unimplemented!() + use anyhow::Context; + + let password = if let Some(password) = &mount.password { + password.clone() + } else { + rpassword::prompt_password("Your password: ")? + }; + + rustcryptfs_mount::mount(&mount.path, &mount.mountpoint, &password) + .context("Failed to run gocryptfs")?; + + Ok(()) }