diff --git a/README.md b/README.md index 92f0464..486fadd 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Project or files: │   ├── robusta_jni │   └── rust_link_a ├── __fs +│   ├── crypt4ghfs-rust │   ├── fuse │   └── fuser ├── __gui @@ -259,6 +260,6 @@ Project or files: ├── vec.rs └── while.rs -228 directories, 38 files +229 directories, 38 files ``` diff --git a/__fs/crypt4ghfs-rust/.gitignore b/__fs/crypt4ghfs-rust/.gitignore new file mode 100644 index 0000000..8efa734 --- /dev/null +++ b/__fs/crypt4ghfs-rust/.gitignore @@ -0,0 +1,9 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +.DS_Store +.vscode \ No newline at end of file diff --git a/__fs/crypt4ghfs-rust/.rustfmt.toml b/__fs/crypt4ghfs-rust/.rustfmt.toml new file mode 100644 index 0000000..ba4b0df --- /dev/null +++ b/__fs/crypt4ghfs-rust/.rustfmt.toml @@ -0,0 +1,68 @@ +max_width = 120 +hard_tabs = true +tab_spaces = 4 +newline_style = "Auto" +use_small_heuristics = "Default" +indent_style = "Block" +wrap_comments = false +format_code_in_doc_comments = true +comment_width = 80 +normalize_comments = true +normalize_doc_attributes = true +license_template_path = "" +format_strings = true +format_macro_matchers = true +format_macro_bodies = true +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Module" +group_imports = "StdExternalCrate" +reorder_imports = true +reorder_modules = true +reorder_impl_items = true +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = false +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_args_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "ClosingNextLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = true +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2021" +version = "Two" +inline_attribute_width = 0 +merge_derives = true +use_try_shorthand = true +use_field_init_shorthand = true +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +required_version = "1.4.38" +unstable_features = true +disable_all_formatting = false +skip_children = false +hide_parse_errors = false +error_on_line_overflow = false +error_on_unformatted = false +report_todo = "Never" +report_fixme = "Never" +ignore = [] +emit_mode = "Files" +make_backup = false diff --git a/__fs/crypt4ghfs-rust/Cargo.lock b/__fs/crypt4ghfs-rust/Cargo.lock new file mode 100644 index 0000000..19987b8 --- /dev/null +++ b/__fs/crypt4ghfs-rust/Cargo.lock @@ -0,0 +1,793 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "crypt4gh" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2e0c28027b2d309e3928de8fe5f02ed5a8a3e4732ddabf91067a5c67ebafa0" +dependencies = [ + "base64", + "bincode", + "clap", + "itertools", + "lazy_static", + "libsodium-sys", + "log", + "pretty_env_logger", + "regex", + "rpassword", + "rust-crypto", + "serde", + "sodiumoxide", + "thiserror", +] + +[[package]] +name = "crypt4ghfs" +version = "0.3.0" +dependencies = [ + "clap", + "crypt4gh", + "fuser", + "itertools", + "log", + "nix", + "pretty_env_logger", + "rpassword", + "serde", + "sodiumoxide", + "syslog", + "thiserror", + "toml", +] + +[[package]] +name = "ed25519" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39" +dependencies = [ + "signature", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuser" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8400a4ea1d18a8302e2952f5137a9a21ab257825ccc7d67db4a8018b89022" +dependencies = [ + "libc", + "log", + "memchr", + "page_size", + "pkg-config", + "smallvec", + "users", + "zerocopy", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09" + +[[package]] +name = "libsodium-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "walkdir", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "num_threads" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c539a50b93a303167eded6e8dff5220cd39447409fb659f4cd24b1f72fe4f133" +dependencies = [ + "libc", +] + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rpassword" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b7ef46d67d4cecf32ad486814d625738e79e4ccd62531dde0548b2f242f894" +dependencies = [ + "libc", + "serde", + "serde_json", + "winapi", +] + +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +dependencies = [ + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time 0.1.44", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signature" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "sodiumoxide" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e26be3acb6c2d9a7aac28482586a7856436af4cfe7100031d219de2d2ecb0028" +dependencies = [ + "ed25519", + "libc", + "libsodium-sys", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e59d925cf59d8151f25a3bedf97c9c157597c9df7324d32d68991cc399ed08b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[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 = "syslog" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978044cc68150ad5e40083c9f6a725e6fd02d7ba1bcf691ec2ff0d66c0b41acc" +dependencies = [ + "error-chain", + "hostname", + "libc", + "log", + "time 0.3.7", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "itoa", + "libc", + "num_threads", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zerocopy" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] diff --git a/__fs/crypt4ghfs-rust/Cargo.toml b/__fs/crypt4ghfs-rust/Cargo.toml new file mode 100644 index 0000000..4b36464 --- /dev/null +++ b/__fs/crypt4ghfs-rust/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "crypt4ghfs" +version = "0.3.0" +authors = ["Roberto "] +edition = "2021" +license = "Apache-2.0" +description = "Fuse layer exposing Crypt4GH-encrypted files, as if they were decrypted" +repository = "https://github.com/EGA-archive/crypt4ghfs-rust" +documentation = "https://docs.rs/crypt4ghfs" +keywords = ["crypt4gh", "genetics", "filesystem", "encryption", "c4gh"] +categories = ["filesystem", "cryptography", "encoding"] +readme = "README.md" + +[lib] +name = "crypt4ghfs" +path = "src/lib.rs" + +[[bin]] +name = "crypt4ghfs" +path = "src/main.rs" + +[dependencies] +# Default +fuser = "0.11" +clap = { version = "3.1", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0" +toml = "0.5" +itertools = "0.10" +rpassword = "6.0" +crypt4gh = "0.4" +nix = "0.23" + +# Logger +pretty_env_logger = "0.4" +log = "0.4" +syslog = "6.0" + +# Decrypting +sodiumoxide = "0.2" + +[profile.release] +lto = true \ No newline at end of file diff --git a/__fs/crypt4ghfs-rust/LICENSE b/__fs/crypt4ghfs-rust/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/__fs/crypt4ghfs-rust/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/__fs/crypt4ghfs-rust/README.md b/__fs/crypt4ghfs-rust/README.md new file mode 100644 index 0000000..10b9d9a --- /dev/null +++ b/__fs/crypt4ghfs-rust/README.md @@ -0,0 +1,75 @@ +> Copied from: https://github.com/EGA-archive/crypt4ghfs-rust + +# Crypt4GH File System (in Rust) + +[![Crates.io](https://img.shields.io/crates/v/crypt4ghfs)](https://crates.io/crates/crypt4ghfs) +[![Docs.rs](https://docs.rs/crypt4ghfs/badge.svg)](https://docs.rs/crypt4ghfs/latest/crypt4ghfs) +![GitHub](https://img.shields.io/github/license/EGA-archive/crypt4ghfs-rust) + +Crypt4GH FUSE File system in Rust. It allows to encrypt and decrypt crypt4gh files in a directory automatically. + +## Installation + +> Requirements: [Rust](https://www.rust-lang.org/tools/install) + +Supported platforms: + +- **Linux** (tested on Ubuntu 20.04) +- **macOS** (up to Big Sur, Monterey does not support FUSE yet) + +```sh +cargo install crypt4ghfs +``` + +## Usage + +The usage of the command is the following: + +```txt +USAGE: + crypt4ghfs [FLAGS] --conf + +ARGS: + + +FLAGS: + -h, --help Prints help information + -v, --verbose Sets the level of verbosity + -V, --version Prints version information + +OPTIONS: + --conf +``` + +## Configuration + +```toml +[DEFAULT] +# Extensions to be detected as encrypted +extensions = ["c4gh"] + +[LOGGER] +# Whether to use syslog or to output to stderr +use_syslog = false +# Level of the logger. Should be one of ["TRACE", "DEBUG", "INFO", "WARN", "CRITICAL"] +log_level = "DEBUG" +# Syslog facility +log_facility = "local2" + +[FUSE] +# The options that will be sent to fuse. The following are available: +# "allow_other", "allow_root", "allow_unmount", "default_permissions", "dev", "no_dev", "suid", "no_suid", "ro", "rw", "exec", "no_exec", "atime", "no_atime", "dir_sync", "sync", "async" +options= ["ro", "default_permissions", "allow_other", "auto_unmount"] +# Path to the root directory of the filesystem +rootdir="tests/rootdir" + +[CRYPT4GH] +# Path to the public keys of the recipients to encrypt to +recipients = ["tests/testfiles/bob.pub"] +# Include log of the crypt4gh encryption / decryption +include_crypt4gh_log = true +# Include my public key to the recipients (so I can decrypt the file too) +include_myself_as_recipient = true +# Path to my private key +seckey = "tests/configs/bob" +``` diff --git a/__fs/crypt4ghfs-rust/app.yaml b/__fs/crypt4ghfs-rust/app.yaml new file mode 100644 index 0000000..cf84d23 --- /dev/null +++ b/__fs/crypt4ghfs-rust/app.yaml @@ -0,0 +1,17 @@ +name: crypt4ghfs +help: Fuse layer exposing Crypt4GH-encrypted files, as if they were decrypted. +args: + - verbose: + required: false + short: v + long: verbose + help: Sets the level of verbosity + takes_value: false + - conf: + required: true + long: conf + #help: TODO + takes_value: true + value_name: conf_path + - MOUNTPOINT: + required: true diff --git a/__fs/crypt4ghfs-rust/justfile b/__fs/crypt4ghfs-rust/justfile new file mode 100644 index 0000000..6c4a63e --- /dev/null +++ b/__fs/crypt4ghfs-rust/justfile @@ -0,0 +1,17 @@ + +run: + echo "The passphrase is 'bob'" + cargo run -- --conf tests/configs/fs.conf tests/mountpoint + +keygen: + crypt4gh keygen --pk testkey.pub --sk testkey.sec + +encrypt: + crypt4gh encrypt --sk tests/keys/testkey.sec --recipient_pk tests/keys/bob.pub < tests/decrypted/file.txt > tests/rootdir/file.txt.c4gh + +decrypt: + echo "The passphrase is 'bob'" + crypt4gh decrypt --sk tests/keys/bob.sec < tests/rootdir/file.txt.c4gh + +umount: + umount tests/mountpoint diff --git a/__fs/crypt4ghfs-rust/src/cli.rs b/__fs/crypt4ghfs-rust/src/cli.rs new file mode 100644 index 0000000..15abe35 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/cli.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +/// Fuse layer exposing Crypt4GH-encrypted files, as if they were decrypted. +#[derive(clap::Parser)] +#[clap(about, version, author)] +pub struct Args { + /// Display debug information + #[clap(short, long)] + pub verbose: bool, + + /// Path to the config file + #[clap(short, long)] + pub conf: PathBuf, + + /// Path to the mountpoint + #[clap()] + pub mountpoint: PathBuf, +} diff --git a/__fs/crypt4ghfs-rust/src/config.rs b/__fs/crypt4ghfs-rust/src/config.rs new file mode 100644 index 0000000..19848c4 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/config.rs @@ -0,0 +1,338 @@ +use std::collections::HashSet; +use std::convert::{TryFrom, TryInto}; +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use crypt4gh::error::Crypt4GHError; +use crypt4gh::Keys; +use fuser::MountOption; +use itertools::Itertools; +use rpassword::prompt_password; +use serde::Deserialize; + +use crate::error::Crypt4GHFSError; + +const PASSPHRASE: &str = "C4GH_PASSPHRASE"; + +#[derive(Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "UPPERCASE")] +pub enum LogLevel { + #[serde(alias = "CRITICAL")] + Critical, + Warn, + Info, + Debug, + #[serde(alias = "NOTSET")] + Trace, +} + +#[derive(Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "snake_case")] +pub enum Facility { + Kern, + User, + Mail, + Daemon, + Auth, + Syslog, + Lpr, + News, + Uucp, + Cron, + Authpriv, + Ftp, + Local0, + Local1, + Local2, + Local3, + Local4, + Local5, + Local6, + Local7, +} + +#[derive(Deserialize, Debug)] +pub struct Default { + extensions: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct Fuse { + options: Option>, + rootdir: String, + cache_directories: Option, +} + +#[derive(Deserialize, Debug)] +pub struct Crypt4GH { + #[serde(rename = "seckey")] + seckey_path: Option, + recipients: Option>, + include_myself_as_recipient: Option, +} + +#[derive(Deserialize, Debug)] +pub struct LoggerConfig { + pub log_level: LogLevel, + pub use_syslog: bool, + pub log_facility: Option, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +pub struct Config { + default: Default, + pub logger: LoggerConfig, + fuse: Fuse, + crypt4gh: Crypt4GH, +} + +impl Config { + pub fn new_with_defaults(rootdir: String, seckey_path: Option) -> Self { + Self { + default: Default { extensions: vec![] }, + fuse: Fuse { + rootdir, + options: Some(vec!["ro".into(), "default_permissions".into(), "auto_unmount".into()]), + cache_directories: Some(true), + }, + crypt4gh: Crypt4GH { + seckey_path, + recipients: Some(vec![]), + include_myself_as_recipient: Some(true), + }, + logger: LoggerConfig { + log_level: LogLevel::Info, + use_syslog: false, + log_facility: None, + }, + } + } + + #[must_use] + pub fn with_extensions(mut self, extensions: Vec) -> Self { + self.default.extensions = extensions; + self + } + + #[must_use] + pub const fn with_log_level(mut self, log_level: LogLevel) -> Self { + self.logger.log_level = log_level; + self + } + + pub fn get_options(&self) -> Vec { + self.fuse.options.clone().map_or_else( + || vec![MountOption::RW, MountOption::DefaultPermissions], + |options| { + options + .iter() + .map(String::as_str) + .map(str_to_mount_option) + .inspect(|option| { + log::info!("+ fuse option: {:?}", option); + }) + .collect() + }, + ) + } + + pub const fn get_cache(&self) -> bool { + if let Some(cache_directories) = self.fuse.cache_directories { + return cache_directories; + } + true + } + + pub fn get_extensions(&self) -> Vec { + self.default.extensions.clone() + } + + pub fn get_secret_key(&self) -> Result>, Crypt4GHFSError> { + match &self.crypt4gh.seckey_path { + Some(seckey_path_str) => { + let seckey_path = Path::new(&seckey_path_str); + log::info!("Loading secret key from {}", seckey_path.display()); + + if !seckey_path.is_file() { + return Err(Crypt4GHFSError::SecretNotFound(seckey_path.into())); + } + + let callback: Box Result> = match std::env::var(PASSPHRASE) { + Ok(_) => { + log::warn!("Warning: Using a passphrase in an environment variable is insecure"); + Box::new(|| std::env::var(PASSPHRASE).map_err(|e| Crypt4GHError::NoPassphrase(e.into()))) + }, + Err(_) => Box::new(|| { + prompt_password(format!("Passphrase for {}: ", seckey_path.display())) + .map_err(|e| Crypt4GHError::NoPassphrase(e.into())) + }), + }; + + let key = crypt4gh::keys::get_private_key(seckey_path, callback) + .map_err(|e| Crypt4GHFSError::SecretKeyError(e.to_string()))?; + + Ok(Some(key)) + }, + None => Ok(None), + } + } + + pub fn get_recipients(&self, seckey: &[u8]) -> HashSet { + let recipient_paths = &self.crypt4gh.recipients.clone().unwrap_or_default(); + + let mut recipient_pubkeys: HashSet<_> = recipient_paths + .iter() + .map(Path::new) + .filter(|path| path.exists()) + .filter_map(|path| { + log::debug!("Recipient pubkey path: {}", path.display()); + crypt4gh::keys::get_public_key(path).ok() + }) + .unique() + .map(|key| Keys { + method: 0, + privkey: seckey.to_vec(), + recipient_pubkey: key, + }) + .collect(); + + if self.crypt4gh.include_myself_as_recipient.unwrap_or(true) { + let k = crypt4gh::keys::get_public_key_from_private_key(seckey) + .expect("Unable to extract public key from seckey"); + recipient_pubkeys.insert(Keys { + method: 0, + privkey: seckey.to_vec(), + recipient_pubkey: k, + }); + } + + recipient_pubkeys + } + + pub const fn get_log_level(&self) -> LogLevel { + self.logger.log_level + } + + pub fn get_rootdir(&self) -> String { + self.fuse.rootdir.to_string() + } + + pub fn from_file(mut config_file: File) -> Result { + let mut config_string = String::new(); + config_file + .read_to_string(&mut config_string) + .map_err(|e| Crypt4GHFSError::BadConfig(e.to_string()))?; + let config_toml = toml::from_str(config_string.as_str()).map_err(|e| Crypt4GHFSError::BadConfig(e.to_string())); + config_toml + } + + pub fn get_facility(&self) -> syslog::Facility { + match self.logger.log_facility.unwrap_or(Facility::User) { + Facility::Kern => syslog::Facility::LOG_KERN, + Facility::User => syslog::Facility::LOG_USER, + Facility::Mail => syslog::Facility::LOG_MAIL, + Facility::Daemon => syslog::Facility::LOG_DAEMON, + Facility::Auth => syslog::Facility::LOG_AUTH, + Facility::Syslog => syslog::Facility::LOG_SYSLOG, + Facility::Lpr => syslog::Facility::LOG_LPR, + Facility::News => syslog::Facility::LOG_NEWS, + Facility::Uucp => syslog::Facility::LOG_UUCP, + Facility::Cron => syslog::Facility::LOG_CRON, + Facility::Authpriv => syslog::Facility::LOG_AUTHPRIV, + Facility::Ftp => syslog::Facility::LOG_FTP, + Facility::Local0 => syslog::Facility::LOG_LOCAL0, + Facility::Local1 => syslog::Facility::LOG_LOCAL1, + Facility::Local2 => syslog::Facility::LOG_LOCAL2, + Facility::Local3 => syslog::Facility::LOG_LOCAL3, + Facility::Local4 => syslog::Facility::LOG_LOCAL4, + Facility::Local5 => syslog::Facility::LOG_LOCAL5, + Facility::Local6 => syslog::Facility::LOG_LOCAL6, + Facility::Local7 => syslog::Facility::LOG_LOCAL7, + } + } + + pub fn setup_logger(&self) -> Result<(), Crypt4GHFSError> { + let log_level: LogLevel = if let Ok(log_level_str) = std::env::var("RUST_LOG") { + log_level_str + .as_str() + .try_into() + .expect("Unable to parse RUST_LOG environment variable") + } + else { + let log_level = self.get_log_level(); + let log_level_str = match log_level { + LogLevel::Critical => "error", + LogLevel::Warn => "warn", + LogLevel::Info => "info", + LogLevel::Debug => "debug", + LogLevel::Trace => "trace", + }; + std::env::set_var("RUST_LOG", log_level_str); + log_level + }; + + // Choose logger + if self.logger.use_syslog { + syslog::init(self.get_facility(), log_level.into(), None)?; + } + else { + let _ = pretty_env_logger::try_init(); // Ignore error + } + + Ok(()) + } +} + +impl TryFrom<&str> for LogLevel { + type Error = Crypt4GHFSError; + + fn try_from(level: &str) -> Result { + match level { + "error" => Ok(Self::Critical), + "warn" => Ok(Self::Warn), + "info" => Ok(Self::Info), + "debug" => Ok(Self::Debug), + "trace" => Ok(Self::Trace), + _ => Err(Crypt4GHFSError::BadConfig("Wrong log level".into())), + } + } +} + +impl From for log::LevelFilter { + fn from(val: LogLevel) -> Self { + match val { + LogLevel::Critical => Self::Error, + LogLevel::Warn => Self::Warn, + LogLevel::Info => Self::Info, + LogLevel::Debug => Self::Debug, + LogLevel::Trace => Self::Trace, + } + } +} + +fn str_to_mount_option(s: &str) -> MountOption { + match s { + "auto_unmount" => MountOption::AutoUnmount, + "allow_other" => MountOption::AllowOther, + "allow_root" => MountOption::AllowRoot, + "default_permissions" => MountOption::DefaultPermissions, + "dev" => MountOption::Dev, + "nodev" => MountOption::NoDev, + "suid" => MountOption::Suid, + "nosuid" => MountOption::NoSuid, + "ro" => MountOption::RO, + "rw" => MountOption::RW, + "exec" => MountOption::Exec, + "noexec" => MountOption::NoExec, + "atime" => MountOption::Atime, + "noatime" => MountOption::NoAtime, + "dirsync" => MountOption::DirSync, + "sync" => MountOption::Sync, + "async" => MountOption::Async, + x if x.starts_with("fsname=") => MountOption::FSName(x[7..].into()), + x if x.starts_with("subtype=") => MountOption::Subtype(x[8..].into()), + x => MountOption::CUSTOM(x.into()), + } +} diff --git a/__fs/crypt4ghfs-rust/src/directory.rs b/__fs/crypt4ghfs-rust/src/directory.rs new file mode 100644 index 0000000..791d441 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/directory.rs @@ -0,0 +1,74 @@ +use std::collections::HashMap; +use std::fs::File; +use std::os::unix::io::AsRawFd; +use std::path::Path; + +use crate::egafile::EgaFile; +use crate::error::{Crypt4GHFSError, Result}; +use crate::utils; + +pub struct Directory { + pub opened_files: HashMap>, + pub path: Box, +} + +impl EgaFile for Directory { + fn fh(&self) -> Vec { + self.opened_files.iter().map(|(&fh, _)| fh).collect() + } + + fn path(&self) -> Box { + self.path.clone() + } + + fn open(&mut self, flags: i32) -> Result { + let path = self.path(); + let directory = utils::open(&path, flags)?; + let fh = directory.as_raw_fd(); + self.opened_files.insert(fh as u64, Box::new(directory)); + Ok(fh) + } + + fn read(&mut self, _fh: u64, _offset: i64, _size: u32) -> Result> { + unimplemented!() + } + + fn flush(&mut self, _fh: u64) -> Result<()> { + unimplemented!() + } + + fn write(&mut self, _fh: u64, _data: &[u8]) -> Result { + unimplemented!() + } + + fn truncate(&mut self, _fh: Option, _size: u64) -> Result<()> { + unimplemented!() + } + + fn close(&mut self, fh: u64) -> Result<()> { + let f = self.opened_files.get(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + assert_eq!(f.as_raw_fd(), fh as i32); + self.opened_files.remove(&fh); + Ok(()) + } + + fn rename(&mut self, new_path: &Path) { + self.path = new_path.into(); + } + + fn attrs(&self, uid: u32, gid: u32) -> Result { + let stat = utils::lstat(&self.path)?; + Ok(utils::stat_to_fileatr(stat, uid, gid)) + } +} + +impl Directory { + pub fn new(file: Option>, path: Box) -> Self { + // Build open files + let mut opened_files = HashMap::new(); + if let Some(f) = file { + opened_files.insert(f.as_raw_fd() as u64, f); + } + Self { opened_files, path } + } +} diff --git a/__fs/crypt4ghfs-rust/src/egafile.rs b/__fs/crypt4ghfs-rust/src/egafile.rs new file mode 100644 index 0000000..22d1b1b --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/egafile.rs @@ -0,0 +1,21 @@ +use std::path::Path; + +use fuser::FileAttr; + +use crate::error::Result; + +pub trait EgaFile { + // Attributes + fn fh(&self) -> Vec; + fn path(&self) -> Box; + + // Filesystem + fn open(&mut self, flags: i32) -> Result; + fn read(&mut self, fh: u64, offset: i64, size: u32) -> Result>; + fn flush(&mut self, fh: u64) -> Result<()>; + fn write(&mut self, fh: u64, data: &[u8]) -> Result; + fn truncate(&mut self, fh: Option, size: u64) -> Result<()>; + fn close(&mut self, fh: u64) -> Result<()>; + fn rename(&mut self, new_path: &Path); + fn attrs(&self, uid: u32, gid: u32) -> Result; +} diff --git a/__fs/crypt4ghfs-rust/src/encrypted_file.rs b/__fs/crypt4ghfs-rust/src/encrypted_file.rs new file mode 100644 index 0000000..2b7c554 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/encrypted_file.rs @@ -0,0 +1,316 @@ +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::unix::io::AsRawFd; +use std::path::Path; + +use chacha20poly1305_ietf::{Key, Nonce}; +use crypt4gh::header::DecryptedHeaderPackets; +use crypt4gh::{Keys, SEGMENT_SIZE}; +use itertools::Itertools; +use sodiumoxide::crypto::aead::chacha20poly1305_ietf; +use sodiumoxide::randombytes::randombytes; + +use crate::egafile::EgaFile; +use crate::error::{Crypt4GHFSError, Result}; +use crate::utils; + +const CIPHER_SEGMENT_SIZE: usize = SEGMENT_SIZE + 28; + +pub struct EncryptedFile { + opened_files: HashMap>, + path: Box, + session_key: [u8; 32], + keys: Vec, + recipient_keys: HashSet, + write_buffer: Vec, + only_read: bool, + + // Optimization + buffer: HashMap, + session_keys: Vec>, + header_len: u64, +} + +#[derive(Debug, Default)] +struct EncryptionBuffer { + data: Vec, + pos: usize, + valid: bool, +} + +impl EgaFile for EncryptedFile { + fn fh(&self) -> Vec { + self.opened_files.iter().map(|(&fh, _)| fh).collect() + } + + fn path(&self) -> Box { + self.path.clone() + } + + fn open(&mut self, flags: i32) -> Result { + // Get path + let mut path_str = self.path().to_string_lossy().to_string(); + path_str.push_str(".c4gh"); + let path = Path::new(&path_str); + + // Open file + let mut file = utils::open(path, flags)?; + let fh = file.as_raw_fd(); + + // Buffer + self.buffer.insert(fh as u64, EncryptionBuffer::default()); + let (keys, header_length) = self.read_header(&mut file).unwrap(); + self.session_keys = keys; + self.header_len = u64::from(header_length); + + // Add to opened files + self.opened_files.insert(fh as u64, Box::new(file)); + + // Return + Ok(fh) + } + + fn read(&mut self, fh: u64, offset: i64, size: u32) -> Result> { + let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + + let first_segment = offset as usize / SEGMENT_SIZE; + let mut off = offset as usize % SEGMENT_SIZE; + + let length = off + size as usize; + let mut nsegments = length / SEGMENT_SIZE; + if length % SEGMENT_SIZE != 0 { + nsegments += 1; + } + + let start_pos = first_segment * SEGMENT_SIZE; + + log::debug!("first_segment: {}", first_segment); + log::debug!("off: {}", off); + log::debug!("length: {}", length); + log::debug!("length % SEGMENT_SIZE != 0: {}", length % SEGMENT_SIZE != 0); + log::debug!("nsegments: {}", nsegments); + log::debug!("start_pos: {}", start_pos); + + let buf = self.buffer.get(&fh).expect("No buffer"); + + if buf.valid && buf.pos <= start_pos && ((start_pos - buf.pos) + length) <= buf.data.len() { + log::debug!("Already have decrypted enough data"); + off += start_pos - buf.pos; + + Ok(buf.data[off..off + size as usize].into()) + } + else { + f.seek(SeekFrom::Start( + self.header_len + first_segment as u64 * CIPHER_SEGMENT_SIZE as u64, + )) + .unwrap(); + + let mut output = Vec::new(); + + for _ in 0..nsegments { + let mut chunk = Vec::with_capacity(CIPHER_SEGMENT_SIZE); + let n = f.take(CIPHER_SEGMENT_SIZE as u64).read_to_end(&mut chunk).unwrap(); + + if n == 0 { + break; + } + + let segment = Self::decrypt_block(&chunk, &self.session_keys); + output.extend_from_slice(&segment); + + if n < CIPHER_SEGMENT_SIZE { + break; + } + } + + log::debug!("Output: {}", output.len()); + + Ok(output[off..(off + size as usize).min(output.len())].into()) + } + } + + fn flush(&mut self, fh: u64) -> Result<()> { + let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + if !self.write_buffer.is_empty() { + log::info!("Writing PARTIAL segment"); + let nonce = Nonce::from_slice(&randombytes(12)).expect("Unable to create nonce from randombytes"); + let key = Key::from_slice(&self.session_key).expect("Unable to create key from session_key"); + let encrypted_segment = crypt4gh::encrypt_segment(&self.write_buffer, nonce, &key); + f.write_all(&encrypted_segment)?; + self.write_buffer.clear(); + } + f.flush()?; + Ok(()) + } + + fn write(&mut self, fh: u64, data: &[u8]) -> Result { + // Write header + if self.only_read { + // Build header + log::debug!("Writing HEADER"); + let header_bytes = crypt4gh::encrypt_header(&self.recipient_keys, &Some(self.session_key)) + .map_err(|e| Crypt4GHFSError::Crypt4GHError(e.to_string()))?; + log::debug!("Header size = {}", header_bytes.len()); + + // Write header + let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + f.write_all(&header_bytes)?; + + // Update status + self.only_read = false; + } + + log::debug!( + "write_buffer.len() = {}, data.len() = {}", + self.write_buffer.len(), + data.len() + ); + + // Chain write buffer with data + let last_segment = self.write_buffer.clone(); + let write_data = last_segment.into_iter().chain(data.iter().copied()); + + let mut new_last_segment = Vec::new(); + for segment in &write_data.chunks(SEGMENT_SIZE) { + // Collect segment + let segment_slice = segment.collect::>(); + log::debug!("segment_slice.len() = {}", segment_slice.len()); + + // This is the last segment, add to the struct + if segment_slice.len() < SEGMENT_SIZE { + log::info!("Storing PARTIAL segment"); + new_last_segment = segment_slice; + } + else { + log::info!("Writing FULL segment"); + // Full segment, write to the file + let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + + // Build encrypted segment + let nonce = Nonce::from_slice(&randombytes(12)).expect("Unable to create nonce from randombytes"); + let key = Key::from_slice(&self.session_key).expect("Unable to create key from session_key"); + let encrypted_segment = crypt4gh::encrypt_segment(&segment_slice, nonce, &key); + + // Write segment + f.write_all(&encrypted_segment)?; + } + } + + // Replace segment buffer + self.write_buffer = new_last_segment; + + // Return + Ok(data.len()) + } + + fn truncate(&mut self, fh: Option, size: u64) -> Result<()> { + log::debug!("Truncate: size = {}", size); + self.opened_files + .iter_mut() + .filter(|(&ffh, _)| fh.is_none() || fh == Some(ffh)) + .try_for_each(|(_, f)| f.set_len(size))?; + Ok(()) + } + + fn close(&mut self, fh: u64) -> Result<()> { + let f = self.opened_files.get(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + assert_eq!(f.as_raw_fd(), fh as i32); + self.opened_files.remove(&fh); + self.write_buffer.clear(); + Ok(()) + } + + fn rename(&mut self, new_path: &Path) { + self.path = new_path.into(); + } + + fn attrs(&self, uid: u32, gid: u32) -> Result { + let mut path_str = self.path.display().to_string(); + path_str.push_str(".c4gh"); + let stat = utils::lstat(Path::new(&path_str))?; + Ok(utils::stat_to_fileatr(stat, uid, gid)) + } +} + +impl EncryptedFile { + pub fn new(file: Option>, path: Box, keys: &[Keys], recipient_keys: &HashSet) -> Self { + // Build session_key + let mut session_key = [0_u8; 32]; + sodiumoxide::randombytes::randombytes_into(&mut session_key); + + // Build open files + let mut opened_files = HashMap::new(); + if let Some(f) = file { + opened_files.insert(f.as_raw_fd() as u64, f); + } + + Self { + opened_files, + path, + session_key, + keys: keys.to_vec(), + recipient_keys: recipient_keys.clone(), + write_buffer: Vec::new(), + only_read: true, + buffer: HashMap::new(), + session_keys: Vec::new(), + header_len: 0, + } + } + + fn read_header(&self, file: &mut File) -> Result<(Vec>, u32)> { + // Get header info + let mut header_length = 16; + let mut temp_buf = [0_u8; 16]; // Size of the header + file.read_exact(&mut temp_buf)?; + + let header_info = crypt4gh::header::deconstruct_header_info(&temp_buf).unwrap(); + + // Calculate header packets + let encrypted_packets = (0..header_info.packets_count) + .map(|_| { + // Get length + let mut length_buffer = [0_u8; 4]; + file.read_exact(&mut length_buffer).unwrap(); + let length = u32::from_le_bytes(length_buffer); + header_length += length; + let length = length - 4; + log::debug!("Packet length: {}", length); + + // Get data + let mut encrypted_data = vec![0_u8; length as usize]; + file.read_exact(&mut encrypted_data).unwrap(); + Ok(encrypted_data) + }) + .collect::>>>()?; + + let DecryptedHeaderPackets { + data_enc_packets: session_keys, + edit_list_packet: edit_list_content, + } = crypt4gh::header::deconstruct_header_body(encrypted_packets, &self.keys, &None).unwrap(); + + assert!(edit_list_content.is_none()); + + Ok((session_keys, header_length)) + } + + fn decrypt_block(ciphersegment: &[u8], session_keys: &[Vec]) -> Vec { + let (nonce_slice, data) = ciphersegment.split_at(12); + let nonce = chacha20poly1305_ietf::Nonce::from_slice(nonce_slice).unwrap(); + + log::debug!("Nonce slice: {:02x?}", nonce_slice.iter().format("")); + log::debug!("Data len = {}", data.len()); + for key in session_keys { + log::debug!("Session keys: {:02x?}", key.iter().format("")); + } + + session_keys + .iter() + .find_map(|key| { + chacha20poly1305_ietf::Key::from_slice(key) + .and_then(|key| chacha20poly1305_ietf::open(data, None, &nonce, &key).ok()) + }) + .unwrap() + } +} diff --git a/__fs/crypt4ghfs-rust/src/error.rs b/__fs/crypt4ghfs-rust/src/error.rs new file mode 100644 index 0000000..29efff7 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/error.rs @@ -0,0 +1,55 @@ +use std::io; +use std::path::PathBuf; + +use nix::errno::Errno; +use thiserror::Error; + +pub type Result = std::result::Result; +const DEFAULT_LIBC_ERROR: Errno = Errno::ECANCELED; + +#[derive(Error, Debug)] +pub enum Crypt4GHFSError { + #[error("Path does not exist (path: {0})")] + PathDoesNotExist(PathBuf), + #[error("Mounting process failed (ERROR = {0})")] + MountError(String), + #[error("Fork failed")] + ForkFailed, + #[error("Secret key not found (path: {0})")] + SecretNotFound(PathBuf), + #[error("Error reading config (ERROR = {0})")] + BadConfig(String), + #[error("Unable to extract secret key (ERROR = {0})")] + SecretKeyError(String), + #[error("Connection url bad format")] + BadConfigConnectionUrl, + #[error("Invalid checksum format")] + InvalidChecksumFormat, + #[error("No checksum found")] + NoChecksum, + #[error("Invalid connection_url scheme: {0}")] + InvalidScheme(String), + #[error("IO Error: {0}")] + IoError(#[from] io::Error), + #[error("CLI configuration failed (ERROR = {0})")] + ConfigError(#[from] clap::Error), + #[error("Config could not configure syslog (ERROR = {0})")] + SysLogError(#[from] syslog::Error), + #[error("Setting logger failed (ERROR = {0})")] + LogError(#[from] log::SetLoggerError), + #[error("File not opened")] + FileNotOpened, + #[error("Crypt4GH failed (ERROR = {0})")] + Crypt4GHError(String), + #[error("Libc failed (ERROR = {0})")] + LibcError(#[from] nix::Error), +} + +impl Crypt4GHFSError { + pub fn to_raw_os_error(&self) -> i32 { + match self { + Self::IoError(io_error) => io_error.raw_os_error().unwrap_or(DEFAULT_LIBC_ERROR as i32), + _ => DEFAULT_LIBC_ERROR as i32, + } + } +} diff --git a/__fs/crypt4ghfs-rust/src/file_admin.rs b/__fs/crypt4ghfs-rust/src/file_admin.rs new file mode 100644 index 0000000..047eff2 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/file_admin.rs @@ -0,0 +1,70 @@ +use std::collections::BTreeMap; +use std::path::Path; + +use crate::directory::Directory; +use crate::egafile::EgaFile; + +type Inode = u64; + +pub struct FileAdmin { + pub inode2file: BTreeMap>, + path2inode: BTreeMap, Inode>, +} + +impl FileAdmin { + pub fn new(rootdir: &str) -> Self { + // Create file admin + let mut file_admin = Self { + inode2file: BTreeMap::new(), + path2inode: BTreeMap::new(), + }; + + // Add rootdir + file_admin.add(1, Box::new(Directory::new(None, Path::new(rootdir).into()))); + + // Return + file_admin + } + + pub fn add(&mut self, ino: u64, file: Box) { + self.path2inode.insert(file.path(), ino); + self.inode2file.insert(ino, file); + } + + pub fn get_by_path(&self, path: &Path) -> Option<&dyn EgaFile> { + self.path2inode.get(path).map(|ino| self.get_file(*ino)) + } + + pub fn get_by_path_mut(&mut self, path: &Path) -> Option<&mut dyn EgaFile> { + self.path2inode + .get(path) + .copied() + .map(move |ino| self.get_file_mut(ino)) + } + + pub fn get_file(&self, ino: u64) -> &dyn EgaFile { + self.inode2file.get(&ino).expect("Unable to get file").as_ref() + } + + pub fn get_file_mut(&mut self, ino: u64) -> &mut dyn EgaFile { + self.inode2file.get_mut(&ino).expect("Unable to get file").as_mut() + } + + pub fn remove(&mut self, ino: u64) -> Option> { + self.inode2file.remove(&ino) + } + + pub fn remove_by_path(&mut self, path: &Path) -> Option> { + // Find inode+ + let mut inode = None; + for (ino, file) in &mut self.inode2file { + if file.path() == path.into() { + inode = Some(*ino); + break; + } + } + + // Remove inode + self.remove(inode?) + } +} diff --git a/__fs/crypt4ghfs-rust/src/filesystem.rs b/__fs/crypt4ghfs-rust/src/filesystem.rs new file mode 100644 index 0000000..7ba04d6 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/filesystem.rs @@ -0,0 +1,458 @@ +use std::collections::{HashMap, HashSet}; +use std::ffi::OsStr; +use std::fs::DirEntry; +use std::os::unix::fs::DirEntryExt; +use std::time::{Duration, SystemTime}; + +use crypt4gh::Keys; +use fuser::{ + Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, + ReplyWrite, Request, TimeOrNow, +}; +use nix::errno::Errno; +use nix::unistd::{Gid, Uid}; + +use crate::file_admin::FileAdmin; +use crate::utils; +// use std::os::linux::fs::MetadataExt; + +const TTL: Duration = Duration::from_secs(300); + +pub struct Crypt4ghFS { + file_admin: FileAdmin, + keys: Vec, + recipients: HashSet, + uid: Uid, + gid: Gid, + entries: HashMap>>, + duration1: std::time::Duration, + // TODO: implement cache directories functionality +} + +impl Crypt4ghFS { + pub fn new(rootdir: &str, seckey: Vec, recipients: HashSet, uid: Uid, gid: Gid) -> Self { + Self { + file_admin: FileAdmin::new(rootdir), + keys: vec![Keys { + method: 0, + privkey: seckey, + recipient_pubkey: vec![], + }], + recipients, + uid, + gid, + entries: HashMap::new(), + duration1: Duration::from_secs(0), + } + } +} + +impl Filesystem for Crypt4ghFS { + // FILESYSTEM + + fn destroy(&mut self) { + log::info!("1 - Elapsed: {:?}", self.duration1); + } + + fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) { + let file = self.file_admin.get_file(ino); + match file.attrs(self.uid.as_raw(), self.gid.as_raw()) { + Ok(attrs) => reply.attr(&TTL, &attrs), + Err(_) => reply.error(1000), + } + } + + fn setattr( + &mut self, + req: &Request<'_>, + ino: u64, + mode: Option, + uid: Option, + gid: Option, + size: Option, + atime: Option, + mtime: Option, + _ctime: Option, + fh: Option, + crtime: Option, + chgtime: Option, + bkuptime: Option, + flags: Option, + reply: ReplyAttr, + ) { + let mut err = None; + + if mode.is_some() { + reply.error(Errno::EPERM as i32); + return; + } + + if uid.is_some() || gid.is_some() { + reply.error(Errno::EPERM as i32); + return; + } + + if atime.is_some() || mtime.is_some() { + reply.error(Errno::EOPNOTSUPP as i32); + return; + } + + if crtime.is_some() || chgtime.is_some() || bkuptime.is_some() || flags.is_some() { + reply.error(Errno::EOPNOTSUPP as i32); + return; + } + + let file = self.file_admin.get_file_mut(ino); + + if let Some(size) = size { + if let Err(e) = file.truncate(fh, size) { + err = Some(e); + } + } + + match err { + None => match file.attrs(req.uid(), req.gid()) { + Ok(attrs) => reply.attr(&TTL, &attrs), + Err(e) => reply.error(e.to_raw_os_error()), + }, + Some(e) => reply.error(e.to_raw_os_error()), + } + } + + fn lookup(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { + let parent_file = self.file_admin.get_file(parent); + + let name_string = name.to_string_lossy().to_string(); + let new_name = name_string.strip_suffix(".c4gh").unwrap_or(&name_string); + let path = parent_file.path().join(new_name); + + match self.file_admin.get_by_path(path.as_path()) { + Some(child_file) => match child_file.attrs(req.uid(), req.gid()) { + Ok(attr) => reply.entry(&TTL, &attr, 0), + Err(e) => reply.error(e.to_raw_os_error()), + }, + None => { + reply.error(Errno::ENOENT as i32); + }, + } + } + + fn statfs(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyStatfs) { + let file = self.file_admin.get_file(ino); + match utils::statfs(&file.path()) { + Ok(statfs) => reply.statfs( + u64::from(statfs.blocks()), + u64::from(statfs.blocks_free()), + u64::from(statfs.blocks_available()), + u64::from(statfs.files()), + u64::from(statfs.files_free()), + statfs.block_size() as u32, + statfs.name_max() as u32, + statfs.fragment_size() as u32, + ), + Err(e) => reply.error(e.to_raw_os_error()), + } + } + + // FILE + + fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { + let file = self.file_admin.get_file_mut(ino); + match file.open(flags) { + Ok(fh) => reply.opened(fh as u64, flags as u32), + Err(e) => reply.error(e.to_raw_os_error()), + } + } + + fn read( + &mut self, + _req: &Request<'_>, + ino: u64, + fh: u64, + offset: i64, + size: u32, + _flags: i32, + _lock_owner: Option, + reply: ReplyData, + ) { + let file = self.file_admin.get_file_mut(ino); + match file.read(fh, offset, size) { + Ok(data) => reply.data(&data), + Err(e) => { + log::error!("{:?}", e); + reply.error(e.to_raw_os_error()); + }, + } + } + + fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, _lock_owner: u64, reply: ReplyEmpty) { + let file = self.file_admin.get_file_mut(ino); + match file.flush(fh) { + Ok(_) => reply.ok(), + Err(e) => reply.error(e.to_raw_os_error()), + } + } + + fn create( + &mut self, + req: &Request<'_>, + parent: u64, + name: &OsStr, + mode: u32, + umask: u32, + flags: i32, + reply: ReplyCreate, + ) { + // Get path + let parent_file = self.file_admin.get_file(parent); + let parent_path = parent_file.path(); + let path = parent_path.join(name).into_boxed_path(); + let mut inbox_path = path.to_path_buf(); + if path.extension().is_none() || path.extension().unwrap() != "c4gh" { + let mut filename = inbox_path.file_name().unwrap().to_string_lossy().to_string(); + filename.push_str(".c4gh"); + inbox_path.set_file_name(filename); + } + + // Create file + log::debug!("Create new file with path: {:?}", inbox_path); + let file = utils::create(&inbox_path, flags, mode & umask).unwrap(); + + // Build file admin entry + let ino = utils::lstat(&inbox_path).unwrap().st_ino; + let egafile = utils::wrap_file(&path, file, &self.keys, &self.recipients); + + // Build reply + let attrs = egafile.attrs(req.uid(), req.gid()).unwrap(); + let fh = *egafile.fh().last().unwrap(); + + // Add and reply + self.file_admin.add(ino, egafile); + reply.created(&TTL, &attrs, 0, fh, flags as u32); + } + + fn write( + &mut self, + _req: &Request<'_>, + ino: u64, + fh: u64, + _offset: i64, + data: &[u8], + _write_flags: u32, + _flags: i32, + _lock_owner: Option, + reply: ReplyWrite, + ) { + let file = self.file_admin.get_file_mut(ino); + // TODO: Warn if offset != 0 => not allowed + match file.write(fh, data) { + Ok(size) => reply.written(size as u32), + Err(e) => reply.error(e.to_raw_os_error()), + } + } + + fn release( + &mut self, + _req: &Request<'_>, + ino: u64, + fh: u64, + _flags: i32, + _lock_owner: Option, + _flush: bool, + reply: ReplyEmpty, + ) { + let file = self.file_admin.get_file_mut(ino); + + file.close(fh).unwrap(); + + reply.ok(); + } + + fn rename( + &mut self, + _req: &Request<'_>, + parent: u64, + name: &OsStr, + newparent: u64, + newname: &OsStr, + _flags: u32, + reply: ReplyEmpty, + ) { + // Build paths + let old_parent_file = self.file_admin.get_file(parent).path(); + let new_parent_path = self.file_admin.get_file(newparent).path(); + let old_path = old_parent_file.join(name); + let new_path = new_parent_path.join(newname); + + // Change paths + let file = self.file_admin.get_by_path_mut(&old_path).unwrap(); + file.rename(new_path.as_path()); + + // Rename + match std::fs::rename(&old_path, new_path) { + Ok(_) => reply.ok(), + Err(e) => reply.error(e.raw_os_error().unwrap()), + } + } + + fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + // Build paths + let parent_file = self.file_admin.get_file(parent); + let parent_path = parent_file.path(); + let path = parent_path.join(name); + + // Remove file + match std::fs::remove_file(path) { + Ok(_) => reply.ok(), + Err(e) => reply.error(e.raw_os_error().unwrap()), + } + } + + // DIRECTORY + + fn readdir(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, skipped: i64, mut reply: ReplyDirectory) { + let _dir = self.file_admin.get_file(ino); + let entries = self.entries.get_mut(&ino).unwrap(); + + // TODO: Make sure that they have always the same order + + let mut last_error = None; + for (offset, entry) in entries[skipped as usize..].iter().enumerate() { + log::debug!("Entry {} - {:?}", offset, entry); + match entry { + Ok(dir_entry) => { + // Track file + let egafile = utils::wrap_path(dir_entry.path().as_path(), &self.keys, &self.recipients); + self.file_admin.add(dir_entry.ino(), egafile); + + // Kind + let kind = utils::get_type(dir_entry); + + // Name + let name = dir_entry.file_name().clone(); + let name_str = name.to_string_lossy().to_string(); + let new_name = name_str.strip_suffix(".c4gh").unwrap_or(&name_str); + + // Add entry + let buffer_full = reply.add(dir_entry.ino(), (skipped + offset as i64 + 1) as i64, kind, new_name); + if buffer_full { + break; + } + }, + Err(e) => { + log::error!("Error on entry {} (ERROR = {:?})", offset, e); + last_error = Some(e.raw_os_error().expect("Unable to convert to error")); + }, + } + } + + match last_error { + None => reply.ok(), + Some(e) => reply.error(e), + } + } + + // fn readdirplus(&mut self, req: &Request<'_>, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectoryPlus) { + // let _dir = self.file_admin.get_file(ino); + // // TODO: Make sure that they have always the same order + // let entries = self.entries.get_mut(&ino).unwrap(); + // let mut last_error = None; + // for (offset, entry) in entries.enumerate().skip(offset as usize) { + // match entry { + // Ok(dir_entry) => { + // let egafile = utils::wrap_path(dir_entry.path().as_path(), &self.keys, &self.recipients); + // self.file_admin.add(dir_entry.ino(), egafile); + // let kind = utils::get_type(&dir_entry); + // let name = dir_entry.file_name().clone(); + // let metadata = dir_entry.metadata().unwrap(); + // let attrs = FileAttr { + // ino: dir_entry.ino(), + // size: dir_entry.metadata().unwrap().len(), + // blocks: metadata.blocks(), + // atime: metadata.accessed().unwrap_or(std::time::UNIX_EPOCH), + // mtime: metadata.modified().unwrap_or(std::time::UNIX_EPOCH), + // ctime: metadata.created().unwrap_or(std::time::UNIX_EPOCH), + // crtime: std::time::UNIX_EPOCH, + // kind, + // perm: metadata.permissions().mode() as u16, + // #[cfg(target_os = "linux")] + // nlink: metadata.st_nlink() as u32, + // #[cfg(target_os = "macos")] + // nlink: 0, + // uid: req.uid(), + // gid: req.gid(), + // #[cfg(target_os = "linux")] + // rdev: metadata.st_rdev() as u32, + // #[cfg(target_os = "macos")] + // rdev: metadata.rdev() as u32, + // #[cfg(target_os = "linux")] + // blksize: metadata.st_blksize() as u32, + // #[cfg(target_os = "macos")] + // blksize: metadata.blksize() as u32, + // padding: 0, + // flags: 0, + // }; + // let buffer_full = reply.add(dir_entry.ino(), (offset + 1) as i64, name, &TTL, &attrs, 0); + // if buffer_full { + // break; + // } + // }, + // Err(e) => { + // last_error = Some(e.raw_os_error().expect("Unable to convert to error")); + // }, + // } + // } + // match last_error { + // None => reply.ok(), + // Some(e) => reply.error(e), + // } + // } + + fn mkdir(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, _mode: u32, _umask: u32, reply: ReplyEntry) { + let parent_file = self.file_admin.get_file(parent); + let parent_path = parent_file.path(); + let path = parent_path.join(name); + match std::fs::create_dir(&path) { + Ok(_) => { + let stat = utils::lstat(&path).unwrap(); + let attrs = utils::stat_to_fileatr(stat, req.uid(), req.gid()); + reply.entry(&TTL, &attrs, 0); + }, + Err(e) => reply.error(e.raw_os_error().expect("Unable to retrieve raw OS error")), + } + } + + fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + let parent_file = self.file_admin.get_file(parent); + let parent_path = parent_file.path(); + let path = parent_path.join(name); + self.file_admin.remove_by_path(&path); + match std::fs::remove_dir(path) { + Ok(_) => reply.ok(), + Err(e) => reply.error(e.raw_os_error().expect("Unable to retrieve raw OS error")), + } + } + + fn opendir(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { + let file = self.file_admin.get_file_mut(ino); + self.entries.insert( + ino, + std::fs::read_dir(file.path()) + .expect("Unable to read directory") + .collect(), + ); + match file.open(flags) { + Ok(fh) => reply.opened(fh as u64, flags as u32), + Err(e) => reply.error(e.to_raw_os_error()), + } + } + + fn releasedir(&mut self, _req: &Request<'_>, ino: u64, fh: u64, _flags: i32, reply: ReplyEmpty) { + let file = self.file_admin.get_file_mut(ino); + self.entries.remove(&ino); + match file.close(fh) { + Ok(_) => reply.ok(), + Err(e) => reply.error(e.to_raw_os_error()), + } + } +} diff --git a/__fs/crypt4ghfs-rust/src/lib.rs b/__fs/crypt4ghfs-rust/src/lib.rs new file mode 100644 index 0000000..990ed07 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/lib.rs @@ -0,0 +1,63 @@ +#![allow( + clippy::upper_case_acronyms, + clippy::missing_panics_doc, + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::module_name_repetitions, + clippy::cast_sign_loss, + clippy::cast_possible_truncation, + clippy::similar_names +)] + +use std::path::{Path, PathBuf}; + +use config::Config; +use error::Crypt4GHFSError; + +pub mod config; +mod directory; +mod egafile; +mod encrypted_file; +pub mod error; +mod file_admin; +mod filesystem; +mod regular_file; +mod utils; + +pub fn run_with_config(conf: &Config, mountpoint: PathBuf) -> Result<(), Crypt4GHFSError> { + // Set log level and logger + conf.setup_logger()?; + + let rootdir = conf.get_rootdir(); + if !Path::new(&rootdir).exists() { + return Err(Crypt4GHFSError::PathDoesNotExist(Path::new(&rootdir).into())); + } + + // Encryption / Decryption keys + let seckey = (conf.get_secret_key()?).map_or_else( + || { + log::warn!("No seckey specified"); + vec![0_u8; 32] + }, + |key| key, + ); + + let recipients = conf.get_recipients(&seckey); + + // Get options + let options = conf.get_options(); + + let fs = filesystem::Crypt4ghFS::new( + &rootdir, + seckey, + recipients, + nix::unistd::getuid(), + nix::unistd::getgid(), + ); + + if !mountpoint.exists() { + return Err(Crypt4GHFSError::PathDoesNotExist(mountpoint)); + } + + fuser::mount2(fs, &mountpoint, &options).map_err(|e| Crypt4GHFSError::MountError(e.to_string())) +} diff --git a/__fs/crypt4ghfs-rust/src/main.rs b/__fs/crypt4ghfs-rust/src/main.rs new file mode 100644 index 0000000..bec9262 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/main.rs @@ -0,0 +1,35 @@ +use std::fs::File; + +use clap::StructOpt; +use crypt4ghfs::error::Crypt4GHFSError; +use crypt4ghfs::{config, run_with_config}; + +use crate::cli::Args; + +mod cli; + +fn run() -> Result<(), Crypt4GHFSError> { + // Init CLI + let matches = Args::parse(); + + let mountpoint = matches.mountpoint; + + // Read config + let config_path = matches.conf; + log::info!("Loading config: {:?}", config_path); + let config_file = File::open(config_path)?; + + let conf = config::Config::from_file(config_file)?; + log::debug!("Config = {:?}", conf); + + // Run + run_with_config(&conf, mountpoint) +} + +fn main() { + if let Err(err) = run() { + let _ = pretty_env_logger::try_init(); + log::error!("{}", err); + std::process::exit(1); + } +} diff --git a/__fs/crypt4ghfs-rust/src/regular_file.rs b/__fs/crypt4ghfs-rust/src/regular_file.rs new file mode 100644 index 0000000..ecc4d85 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/regular_file.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::unix::io::AsRawFd; +use std::path::Path; + +use crate::egafile::EgaFile; +use crate::error::{Crypt4GHFSError, Result}; +use crate::utils; + +pub struct RegularFile { + pub opened_files: HashMap>, + pub path: Box, + pub only_read: bool, +} + +impl EgaFile for RegularFile { + fn fh(&self) -> Vec { + self.opened_files.iter().map(|(&fh, _)| fh).collect() + } + + fn path(&self) -> Box { + self.path.clone() + } + + fn open(&mut self, flags: i32) -> Result { + let path = self.path(); + let file = utils::open(&path, flags)?; + let fh = file.as_raw_fd(); + self.opened_files.insert(fh as u64, Box::new(file)); + Ok(fh) + } + + fn read(&mut self, fh: u64, offset: i64, size: u32) -> Result> { + let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + let mut data = Vec::new(); + f.seek(SeekFrom::Start(offset as u64))?; + f.as_ref().take(u64::from(size)).read_to_end(&mut data)?; + Ok(data) + } + + fn flush(&mut self, fh: u64) -> Result<()> { + let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + f.flush()?; + Ok(()) + } + + fn write(&mut self, fh: u64, data: &[u8]) -> Result { + self.only_read = false; + let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + + // Write data + f.write_all(data)?; + Ok(data.len()) + } + + fn truncate(&mut self, fh: Option, size: u64) -> Result<()> { + log::debug!("Truncate: size = {}", size); + self.opened_files + .iter_mut() + .filter(|(&ffh, _)| fh.is_none() || fh == Some(ffh)) + .try_for_each(|(_, f)| f.set_len(size))?; + Ok(()) + } + + fn close(&mut self, fh: u64) -> Result<()> { + let f = self.opened_files.get(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?; + assert_eq!(f.as_raw_fd(), fh as i32); + self.opened_files.remove(&fh); + self.only_read = true; + Ok(()) + } + + fn rename(&mut self, new_path: &Path) { + self.path = new_path.into(); + } + + fn attrs(&self, uid: u32, gid: u32) -> Result { + let stat = utils::lstat(&self.path)?; + Ok(utils::stat_to_fileatr(stat, uid, gid)) + } +} + +impl RegularFile { + pub fn new(file: Option>, path: Box) -> Self { + // Build open files + let mut opened_files = HashMap::new(); + if let Some(f) = file { + opened_files.insert(f.as_raw_fd() as u64, f); + } + + // Build RegularFile object + Self { + opened_files, + path, + only_read: true, + } + } +} diff --git a/__fs/crypt4ghfs-rust/src/utils.rs b/__fs/crypt4ghfs-rust/src/utils.rs new file mode 100644 index 0000000..c83dc49 --- /dev/null +++ b/__fs/crypt4ghfs-rust/src/utils.rs @@ -0,0 +1,149 @@ +use std::collections::HashSet; +use std::fs::{DirEntry, File, OpenOptions}; +use std::io; +use std::os::unix::fs::OpenOptionsExt; +use std::path::Path; +use std::time::{Duration, SystemTime}; + +use crypt4gh::Keys; +use fuser::{FileAttr, FileType}; +use nix::fcntl::OFlag; +use nix::sys::stat::{FileStat, Mode, SFlag}; +use nix::sys::statvfs::Statvfs; + +use crate::directory::Directory; +use crate::egafile::EgaFile; +use crate::encrypted_file::EncryptedFile; +use crate::error::Result; +use crate::regular_file::RegularFile; + +pub fn lstat(path: &Path) -> Result { + let stat = nix::sys::stat::lstat(path)?; + Ok(stat) +} + +pub fn stat_to_fileatr(stat: FileStat, uid: u32, gid: u32) -> FileAttr { + let mut perm = Mode::from_bits_truncate(stat.st_mode); + perm.set(Mode::S_IRWXG, false); + perm.set(Mode::S_IRWXO, false); + + let kind = match SFlag::from_bits_truncate(stat.st_mode) & SFlag::S_IFMT { + SFlag::S_IFDIR => FileType::Directory, + SFlag::S_IFREG => FileType::RegularFile, + SFlag::S_IFLNK => FileType::Symlink, + SFlag::S_IFBLK => FileType::BlockDevice, + SFlag::S_IFCHR => FileType::CharDevice, + SFlag::S_IFIFO => FileType::NamedPipe, + SFlag::S_IFSOCK => FileType::Socket, + _ => panic!("Unknown file type"), + }; + + FileAttr { + ino: stat.st_ino, + size: stat.st_size as u64, + blocks: stat.st_blocks as u64, + atime: SystemTime::UNIX_EPOCH + + Duration::from_secs(stat.st_atime as u64) + + Duration::from_nanos(stat.st_atime_nsec as u64), + mtime: SystemTime::UNIX_EPOCH + + Duration::from_secs(stat.st_mtime as u64) + + Duration::from_nanos(stat.st_mtime_nsec as u64), + ctime: SystemTime::UNIX_EPOCH + + Duration::from_secs(stat.st_ctime as u64) + + Duration::from_nanos(stat.st_ctime_nsec as u64), + crtime: SystemTime::UNIX_EPOCH, // TODO: Is this one okay? + kind, + perm: perm.bits() as u16, + #[cfg(target_os = "macos")] + nlink: u32::from(stat.st_nlink), + #[cfg(target_os = "linux")] + nlink: stat.st_nlink as u32, + uid, + gid, + rdev: stat.st_rdev as u32, + blksize: stat.st_blksize as u32, + flags: 0, + } +} + +pub fn get_type(entry: &DirEntry) -> fuser::FileType { + let kind = entry.file_type().expect("Unable to get file type"); + if kind.is_file() { + fuser::FileType::RegularFile + } + else if kind.is_dir() { + fuser::FileType::Directory + } + else if kind.is_symlink() { + fuser::FileType::Symlink + } + else { + panic!("Unknown file type"); + } +} + +pub fn open(path: &Path, flags: i32) -> io::Result { + let open_flags = OFlag::from_bits_truncate(flags); + OpenOptions::new() + .custom_flags(flags) + .read(open_flags.contains(OFlag::O_RDONLY) || open_flags.contains(OFlag::O_RDWR)) + .write(open_flags.contains(OFlag::O_WRONLY) || open_flags.contains(OFlag::O_RDWR)) + .open(path) +} + +pub fn create(path: &Path, flags: i32, _mode: u32) -> io::Result { + let open_flags = OFlag::from_bits_truncate(flags); + OpenOptions::new() + .custom_flags(flags) + //.mode(mode) + .read(open_flags.contains(OFlag::O_RDONLY) || open_flags.contains(OFlag::O_RDWR)) + .write(open_flags.contains(OFlag::O_WRONLY) || open_flags.contains(OFlag::O_RDWR)) + .open(path) +} + +pub fn wrap_file(path: &Path, file: File, keys: &[Keys], recipient_keys: &HashSet) -> Box { + wrapper(path.into(), Some(Box::new(file)), keys, recipient_keys) +} + +pub fn wrap_path(path: &Path, keys: &[Keys], recipient_keys: &HashSet) -> Box { + wrapper(path.into(), None, keys, recipient_keys) +} + +fn wrapper( + path: Box, + file: Option>, + keys: &[Keys], + recipient_keys: &HashSet, +) -> Box { + match path.extension() { + Some(ext) if ext != "c4gh" => Box::new(RegularFile::new(file, path)), + Some(_) => { + let mut inbox_path = path.to_path_buf(); + let mut filename = inbox_path.file_name().unwrap().to_string_lossy().to_string(); + filename = filename.strip_suffix(".c4gh").unwrap().to_string(); + inbox_path.set_file_name(filename); + Box::new(EncryptedFile::new( + file, + inbox_path.into_boxed_path(), + keys, + recipient_keys, + )) + }, + None => { + if path.is_file() { + Box::new(EncryptedFile::new(file, path, keys, recipient_keys)) + } + else if path.is_dir() { + Box::new(Directory::new(file, path)) + } + else { + panic!("Unknown file: {:?}", path) + } + }, + } +} + +pub fn statfs(path: &Path) -> Result { + let statvfs = nix::sys::statvfs::statvfs(path)?; + Ok(statvfs) +} diff --git a/__fs/crypt4ghfs-rust/tests/configs/fs.conf b/__fs/crypt4ghfs-rust/tests/configs/fs.conf new file mode 100644 index 0000000..163ea7c --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/configs/fs.conf @@ -0,0 +1,19 @@ +[DEFAULT] +extensions = ["c4gh"] + +[LOGGER] +use_syslog = false +log_level = "DEBUG" +log_facility = "local2" + +[FUSE] +options= ["ro", "default_permissions", "allow_other", "auto_unmount"] +rootdir="tests/rootdir" + +[CRYPT4GH] +recipients = ["tests/keys/bob.pub"] +include_crypt4gh_log = true +include_myself_as_recipient = true + +# The decryption key +seckey = "tests/keys/bob.sec" diff --git a/__fs/crypt4ghfs-rust/tests/decrypted/file.txt b/__fs/crypt4ghfs-rust/tests/decrypted/file.txt new file mode 100644 index 0000000..ad76585 --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/decrypted/file.txt @@ -0,0 +1 @@ +This is a test file. Can you decrypt it? \ No newline at end of file diff --git a/__fs/crypt4ghfs-rust/tests/keys/bob.pub b/__fs/crypt4ghfs-rust/tests/keys/bob.pub new file mode 100644 index 0000000..0951cc9 --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/keys/bob.pub @@ -0,0 +1,3 @@ +-----BEGIN CRYPT4GH PUBLIC KEY----- +JXGe7vpNZpWEhsxrIE/h83xst7sQ+2INpaoiGjtLIDg= +-----END CRYPT4GH PUBLIC KEY----- diff --git a/__fs/crypt4ghfs-rust/tests/keys/bob.sec b/__fs/crypt4ghfs-rust/tests/keys/bob.sec new file mode 100644 index 0000000..e6d0106 --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/keys/bob.sec @@ -0,0 +1,3 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +YzRnaC12MQAGYmNyeXB0ABQAAABkb1LLjyLNrcL4IgMD+NuDDQARY2hhY2hhMjBfcG9seTEzMDUAPFfaFm7bJc+pr6IRezakf5AsP7HTZnVfhSBt7XIKQcJBJY/yrPSfLxLvPMY4Edu4r0hyJTX2CNqR7wmwYg== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/__fs/crypt4ghfs-rust/tests/keys/testkey.pub b/__fs/crypt4ghfs-rust/tests/keys/testkey.pub new file mode 100644 index 0000000..10da58d --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/keys/testkey.pub @@ -0,0 +1,3 @@ +-----BEGIN CRYPT4GH PUBLIC KEY----- +hf6GsKxIePbdu+hIYpyIDSPh4h15DzTsMT4eqkw3yRw= +-----END CRYPT4GH PUBLIC KEY----- diff --git a/__fs/crypt4ghfs-rust/tests/keys/testkey.sec b/__fs/crypt4ghfs-rust/tests/keys/testkey.sec new file mode 100644 index 0000000..f062618 --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/keys/testkey.sec @@ -0,0 +1,3 @@ +-----BEGIN CRYPT4GH PRIVATE KEY----- +YzRnaC12MQAEbm9uZQAEbm9uZQAgoQNTIKvd6MOvcyCgI3qvPd+t6gT2ut6PYSri74rK0T4= +-----END CRYPT4GH PRIVATE KEY----- diff --git a/__fs/crypt4ghfs-rust/tests/rootdir/file.txt.c4gh b/__fs/crypt4ghfs-rust/tests/rootdir/file.txt.c4gh new file mode 100644 index 0000000..3075f67 Binary files /dev/null and b/__fs/crypt4ghfs-rust/tests/rootdir/file.txt.c4gh differ diff --git a/__fs/crypt4ghfs-rust/tests/testfiles/alice.pub b/__fs/crypt4ghfs-rust/tests/testfiles/alice.pub new file mode 100644 index 0000000..0dfe3c2 --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/testfiles/alice.pub @@ -0,0 +1,3 @@ +-----BEGIN CRYPT4GH PUBLIC KEY----- +oyERnWAhzV4MAh9XIk0xD4C+nNp2tpLUiWtQoVS/xB4= +-----END CRYPT4GH PUBLIC KEY----- diff --git a/__fs/crypt4ghfs-rust/tests/testfiles/alice.sec b/__fs/crypt4ghfs-rust/tests/testfiles/alice.sec new file mode 100644 index 0000000..3e9f752 --- /dev/null +++ b/__fs/crypt4ghfs-rust/tests/testfiles/alice.sec @@ -0,0 +1,3 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +YzRnaC12MQAGYmNyeXB0ABQAAABk8Kn90WJVzJBevxN4980aWwARY2hhY2hhMjBfcG9seTEzMDUAPBdXfpV1zOcMg5EJRlGNpKZXT4PXM2iraMGCyomRQqWaH5iBGmJXU/JROPsyoX5nqmNo8oxANvgDi1hqZQ== +-----END ENCRYPTED PRIVATE KEY-----