Merge pull request #6 from oupson/2-mount-a-gocryptfs-filesystem-on-linux
2 mount a gocryptfs filesystem on linux
This commit is contained in:
commit
004a9b4b54
|
@ -50,18 +50,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
version = "0.7.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.57"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
|
||||
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
|
@ -100,9 +100,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
@ -116,6 +116,12 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -143,16 +149,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.18"
|
||||
version = "3.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
|
||||
checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
|
@ -160,9 +166,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.1.18"
|
||||
version = "3.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
|
||||
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
|
@ -173,27 +179,27 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.0"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.2"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
|
||||
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.3"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
|
@ -210,9 +216,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
|
@ -230,9 +236,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
|
||||
checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
|
@ -242,10 +248,25 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.5"
|
||||
name = "fuser"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||
checksum = "104ed58f182bc2975062cd3fab229e82b5762de420e26cf5645f661402694599"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"page_size",
|
||||
"smallvec",
|
||||
"users",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
|
@ -253,9 +274,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
@ -274,9 +295,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
@ -319,9 +340,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.1"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
|
@ -339,21 +360,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.2"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
version = "0.2.134"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
@ -370,6 +385,12 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
|
@ -378,9 +399,19 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.0.1"
|
||||
version = "6.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435"
|
||||
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||
|
||||
[[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 = "password-hash"
|
||||
|
@ -440,36 +471,36 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.39"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.18"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.6"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -478,9 +509,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.26"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustcryptfs"
|
||||
|
@ -490,11 +531,24 @@ dependencies = [
|
|||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
"rpassword",
|
||||
"rustcryptfs-lib",
|
||||
"rustcryptfs-mount",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustcryptfs-fuse"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fuser",
|
||||
"libc",
|
||||
"log",
|
||||
"rustcryptfs-lib",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustcryptfs-lib"
|
||||
version = "0.1.0"
|
||||
|
@ -512,11 +566,18 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustcryptfs-mount"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rustcryptfs-fuse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.10"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "salsa20"
|
||||
|
@ -542,18 +603,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.137"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.137"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
||||
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -562,9 +623,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.81"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
|
||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -573,15 +634,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -596,15 +663,27 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.95"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
|
||||
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
|
||||
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"
|
||||
|
@ -616,24 +695,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.31"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.31"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -648,9 +727,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.0"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||
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"
|
||||
|
@ -662,6 +747,16 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -670,9 +765,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
|
@ -704,3 +799,24 @@ 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",
|
||||
]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"rustcryptfs",
|
||||
"rustcryptfs-lib"
|
||||
"rustcryptfs-lib",
|
||||
"rustcryptfs-fuse",
|
||||
"rustcryptfs-mount"
|
||||
]
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
# RustCryptFS
|
||||
An implementation of [gocryptfs](https://github.com/rfjakob/gocryptfs) in Rust.
|
||||
|
||||
## Supported plaforms and features
|
||||
- [x] Linux (via FUSE)
|
||||
- [x] read
|
||||
- [ ] write
|
||||
- [ ] Windows
|
||||
- [ ] read
|
||||
- [ ] write
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "rustcryptfs-fuse"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
fuser = { version = "0.11", default-features = false }
|
||||
log = "0.4"
|
||||
rustcryptfs-lib = { path = "../rustcryptfs-lib" }
|
||||
thiserror = "1.0"
|
||||
libc = "0.2"
|
|
@ -0,0 +1,382 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
ffi::OsStr,
|
||||
fs::{File, FileType as StdFileType},
|
||||
io::{Error as IoError, Read, Result as IoResult, Seek, SeekFrom},
|
||||
ops::Add,
|
||||
os::unix::prelude::{FileTypeExt, MetadataExt, OsStrExt, PermissionsExt},
|
||||
path::{Path, PathBuf},
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use fuser::{FileAttr, FileType, Filesystem, FUSE_ROOT_ID};
|
||||
use rustcryptfs_lib::{content::ContentEnc, GocryptFs};
|
||||
|
||||
use crate::{
|
||||
error::{ErrorExt, Result},
|
||||
inode_cache::{InodeCache, InodeCacheExt},
|
||||
};
|
||||
|
||||
trait OptionExt<R> {
|
||||
fn enoent(self) -> IoResult<R>;
|
||||
}
|
||||
|
||||
impl<R> OptionExt<R> for Option<R> {
|
||||
fn enoent(self) -> IoResult<R> {
|
||||
match self {
|
||||
Some(r) => Ok(r),
|
||||
None => Err(IoError::from_raw_os_error(libc::ENOENT)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BLOCK_SIZE: u64 = 4096;
|
||||
|
||||
pub struct EncryptedFs {
|
||||
fs: GocryptFs,
|
||||
inode_cache: InodeCache,
|
||||
}
|
||||
|
||||
impl EncryptedFs {
|
||||
pub fn new<P>(path: P, password: &str) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
|
||||
log::info!("Opening dir ...");
|
||||
let fs = GocryptFs::open(path, password)?;
|
||||
|
||||
println!("Filesystem mounted and ready.");
|
||||
|
||||
let mut inode_cache = BTreeMap::new();
|
||||
inode_cache.insert(FUSE_ROOT_ID, path.to_path_buf());
|
||||
|
||||
Ok(Self { fs, inode_cache })
|
||||
}
|
||||
|
||||
pub fn mount<P>(self, mountpoint: P) -> std::io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
fuser::mount2(self, mountpoint, &[])
|
||||
}
|
||||
|
||||
fn get_file_type(file_type: StdFileType) -> FileType {
|
||||
if file_type.is_file() {
|
||||
FileType::RegularFile
|
||||
} else if file_type.is_dir() {
|
||||
FileType::Directory
|
||||
} else if file_type.is_symlink() {
|
||||
FileType::Symlink
|
||||
} else if file_type.is_socket() {
|
||||
FileType::Socket
|
||||
} else if file_type.is_char_device() {
|
||||
FileType::CharDevice
|
||||
} else if file_type.is_block_device() {
|
||||
FileType::BlockDevice
|
||||
} else if file_type.is_fifo() {
|
||||
FileType::NamedPipe
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path(&self, ino: u64) -> Option<&PathBuf> {
|
||||
self.inode_cache.get_path(ino)
|
||||
}
|
||||
|
||||
fn get_attr<P>(path: P, ino: u64) -> std::io::Result<FileAttr>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let meta = std::fs::metadata(&path)?;
|
||||
|
||||
let file_type = Self::get_file_type(meta.file_type());
|
||||
|
||||
let file_size = if meta.is_file() {
|
||||
ContentEnc::get_real_size(meta.size())
|
||||
} else {
|
||||
meta.size()
|
||||
};
|
||||
|
||||
Ok(FileAttr {
|
||||
ino,
|
||||
size: file_size,
|
||||
blocks: (file_size + BLOCK_SIZE - 1) / BLOCK_SIZE,
|
||||
atime: meta.accessed()?,
|
||||
mtime: meta.modified()?,
|
||||
ctime: UNIX_EPOCH.add(Duration::new(meta.ctime() as u64, 0)),
|
||||
crtime: UNIX_EPOCH.add(Duration::new(meta.ctime() as u64, 0)),
|
||||
kind: file_type,
|
||||
perm: meta.permissions().mode() as u16,
|
||||
nlink: meta.nlink() as u32,
|
||||
uid: meta.uid(),
|
||||
gid: meta.gid(),
|
||||
rdev: 0,
|
||||
blksize: BLOCK_SIZE as u32,
|
||||
flags: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn lookup_impl(
|
||||
&mut self,
|
||||
parent: u64,
|
||||
name: &std::ffi::OsStr,
|
||||
) -> rustcryptfs_lib::error::Result<(Duration, FileAttr, u64)> {
|
||||
let parent = self.get_path(parent).enoent()?;
|
||||
let iv = std::fs::read(parent.join("gocryptfs.diriv"))?;
|
||||
let dir_decoder = self.fs.filename_decoder().get_cipher_for_dir(&iv);
|
||||
|
||||
let encrypted_name = dir_decoder.encrypt_filename(&name.to_string_lossy())?;
|
||||
|
||||
let encrypted_name = match &encrypted_name {
|
||||
rustcryptfs_lib::filename::EncodedFilename::ShortFilename(s) => s,
|
||||
rustcryptfs_lib::filename::EncodedFilename::LongFilename(l) => l.filename(),
|
||||
};
|
||||
|
||||
let file_path = parent.join(encrypted_name);
|
||||
|
||||
if file_path.exists() {
|
||||
let (ino, file_path) = self.inode_cache.get_or_insert_inode(file_path);
|
||||
|
||||
Ok((Duration::new(0, 0), Self::get_attr(file_path, ino)?, 0))
|
||||
} else {
|
||||
Err(IoError::from_raw_os_error(libc::ENOENT).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_dir_impl(
|
||||
&mut self,
|
||||
ino: u64,
|
||||
offset: i64,
|
||||
reply: &mut fuser::ReplyDirectory,
|
||||
) -> rustcryptfs_lib::error::Result<()> {
|
||||
let folder_path = &self.inode_cache.get_path(ino).enoent()?.clone();
|
||||
let iv = std::fs::read(folder_path.join("gocryptfs.diriv"))?;
|
||||
|
||||
let dir_decoder = self.fs.filename_decoder().get_cipher_for_dir(&iv);
|
||||
|
||||
if offset == 0 {
|
||||
let ino_parent = if ino == FUSE_ROOT_ID {
|
||||
FUSE_ROOT_ID
|
||||
} else {
|
||||
let parent = folder_path.parent().enoent()?;
|
||||
self.inode_cache
|
||||
.iter()
|
||||
.find_map(|(ino, p)| if p == parent { Some(*ino) } else { None })
|
||||
.enoent()?
|
||||
};
|
||||
|
||||
if !reply.add(ino, 1, FileType::Directory, ".") {
|
||||
if reply.add(ino_parent, 2, FileType::Directory, "..") {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
for (index, (meta, encrypted_name, name)) in std::fs::read_dir(folder_path)?
|
||||
.flat_map(|e| e.ok())
|
||||
.flat_map(|dir| match extract_name(&dir, folder_path, &dir_decoder) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to extract name of entry {:?} : {}",
|
||||
dir.file_name(),
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.skip(offset as usize)
|
||||
.enumerate()
|
||||
{
|
||||
let (inode, _) = self
|
||||
.inode_cache
|
||||
.get_or_insert_inode(folder_path.join(&encrypted_name));
|
||||
|
||||
let file_type = Self::get_file_type(meta.file_type());
|
||||
|
||||
let buffer_full: bool = reply.add(
|
||||
inode,
|
||||
offset + index as i64 + 1 + 2,
|
||||
file_type,
|
||||
OsStr::from_bytes(name.as_bytes()),
|
||||
);
|
||||
|
||||
if buffer_full {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_impl(
|
||||
&mut self,
|
||||
ino: u64,
|
||||
offset: i64,
|
||||
size: usize,
|
||||
) -> rustcryptfs_lib::error::Result<Vec<u8>> {
|
||||
let file_path = self.get_path(ino).enoent()?;
|
||||
let mut file = File::open(file_path)?;
|
||||
let decoder = self.fs.content_decoder();
|
||||
|
||||
let mut buf = [0u8; 18];
|
||||
let n = file.read(&mut buf)?;
|
||||
let id = if n < 18 { None } else { Some(&buf[2..]) };
|
||||
|
||||
let mut block_index = offset as u64 / 4096;
|
||||
|
||||
let mut buffer = Vec::with_capacity(size);
|
||||
|
||||
let mut rem = size;
|
||||
|
||||
let mut buf = [0u8; 4096 + 32];
|
||||
|
||||
file.seek(SeekFrom::Start(18 + block_index * (4096 + 32)))?;
|
||||
|
||||
{
|
||||
let n = file.read(&mut buf)?;
|
||||
|
||||
let res = decoder.decrypt_block(&buf[..n], block_index, id)?;
|
||||
|
||||
let seek = (offset as u64 - block_index * 4096) as usize;
|
||||
buffer.extend_from_slice(&res[seek..]);
|
||||
|
||||
block_index += 1;
|
||||
|
||||
rem -= res.len() - seek;
|
||||
}
|
||||
|
||||
while rem > 0 {
|
||||
let n = file.read(&mut buf)?;
|
||||
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let res = decoder.decrypt_block(&buf[..n], block_index, id)?;
|
||||
|
||||
let size = res.len().min(rem);
|
||||
|
||||
buffer.extend_from_slice(&res[..size]);
|
||||
|
||||
block_index += 1;
|
||||
|
||||
rem -= size;
|
||||
}
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Filesystem for EncryptedFs {
|
||||
fn access(
|
||||
&mut self,
|
||||
_req: &fuser::Request<'_>,
|
||||
ino: u64,
|
||||
_mask: i32,
|
||||
reply: fuser::ReplyEmpty,
|
||||
) {
|
||||
if let Some(_path) = self.get_path(ino) {
|
||||
reply.ok()
|
||||
} else {
|
||||
reply.error(libc::ENOENT)
|
||||
}
|
||||
}
|
||||
|
||||
fn getattr(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) {
|
||||
if let Some(path) = self.get_path(ino) {
|
||||
match Self::get_attr(path, ino) {
|
||||
Ok(attr) => reply.attr(&Duration::new(0, 0), &attr),
|
||||
Err(e) => reply.error(e.raw_os_error().unwrap()),
|
||||
}
|
||||
} else {
|
||||
reply.error(libc::ENOENT)
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(
|
||||
&mut self,
|
||||
_req: &fuser::Request<'_>,
|
||||
parent: u64,
|
||||
name: &std::ffi::OsStr,
|
||||
reply: fuser::ReplyEntry,
|
||||
) {
|
||||
match self.lookup_impl(parent, name) {
|
||||
Ok((ttl, attr, generation)) => reply.entry(&ttl, &attr, generation),
|
||||
Err(e) => {
|
||||
log::debug!("error on lookup : {}", e);
|
||||
reply.error(e.to_raw_code())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn readdir(
|
||||
&mut self,
|
||||
_req: &fuser::Request<'_>,
|
||||
ino: u64,
|
||||
_fh: u64,
|
||||
offset: i64,
|
||||
mut reply: fuser::ReplyDirectory,
|
||||
) {
|
||||
match self.read_dir_impl(ino, offset, &mut reply) {
|
||||
Ok(()) => reply.ok(),
|
||||
Err(e) => {
|
||||
log::debug!("error on readdir : {}", e);
|
||||
reply.error(e.to_raw_code())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
_req: &fuser::Request<'_>,
|
||||
ino: u64,
|
||||
_fh: u64,
|
||||
offset: i64,
|
||||
size: u32,
|
||||
_flags: i32,
|
||||
_lock_owner: Option<u64>,
|
||||
reply: fuser::ReplyData,
|
||||
) {
|
||||
match self.read_impl(ino, offset, size as usize) {
|
||||
Ok(data) => reply.data(&data),
|
||||
Err(e) => {
|
||||
log::error!("read : {}", e);
|
||||
reply.error(e.to_raw_code())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_name(
|
||||
dir: &std::fs::DirEntry,
|
||||
folder_path: &Path,
|
||||
dir_decoder: &rustcryptfs_lib::filename::DirFilenameCipher,
|
||||
) -> rustcryptfs_lib::error::Result<Option<(std::fs::Metadata, String, String)>> {
|
||||
let filename = dir.file_name();
|
||||
let filename = filename.to_string_lossy();
|
||||
if filename != "gocryptfs.conf" && filename != "gocryptfs.diriv" {
|
||||
if filename.starts_with("gocryptfs.longname.") {
|
||||
if !filename.ends_with(".name") {
|
||||
let filename =
|
||||
std::fs::read_to_string(folder_path.join(format!("{}.name", filename)))?;
|
||||
let decrypted_filename = dir_decoder.decode_filename(filename.as_str())?;
|
||||
Ok(Some((dir.metadata()?, filename, decrypted_filename)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
let decrypted_filename = dir_decoder.decode_filename(&*filename)?;
|
||||
Ok(Some((
|
||||
dir.metadata()?,
|
||||
filename.to_string(),
|
||||
decrypted_filename,
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use rustcryptfs_lib::filename::FilenameCipherError;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
RustCryptFsError(#[from] rustcryptfs_lib::error::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
RustCryptFsFilenameError(#[from] FilenameCipherError),
|
||||
}
|
||||
|
||||
|
||||
pub(crate) trait ErrorExt {
|
||||
fn to_raw_code(&self) -> i32;
|
||||
}
|
||||
|
||||
impl ErrorExt for rustcryptfs_lib::error::Error {
|
||||
fn to_raw_code(&self) -> i32 {
|
||||
match self {
|
||||
rustcryptfs_lib::error::Error::FilenameCipherError(_) => libc::EIO,
|
||||
rustcryptfs_lib::error::Error::ContentCipherError(_) => libc::EIO,
|
||||
rustcryptfs_lib::error::Error::ConfigError(_) => todo!(),
|
||||
rustcryptfs_lib::error::Error::JsonError(_) => todo!(),
|
||||
rustcryptfs_lib::error::Error::IoError(e) => e.raw_os_error().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
use std::{collections::BTreeMap, path::PathBuf};
|
||||
|
||||
pub(crate) type InodeCache = BTreeMap<u64, PathBuf>;
|
||||
|
||||
pub(crate) trait InodeCacheExt {
|
||||
fn get_or_insert_inode(&mut self, file_path: PathBuf) -> (u64, PathBuf);
|
||||
|
||||
fn get_path(&self, ino: u64) -> Option<&PathBuf>;
|
||||
}
|
||||
|
||||
impl InodeCacheExt for InodeCache {
|
||||
// TODO Try to avoid clone
|
||||
fn get_or_insert_inode(&mut self, file_path: PathBuf) -> (u64, PathBuf) {
|
||||
if let Some((ino, path)) =
|
||||
self.iter()
|
||||
.find_map(|(i, p)| if p.eq(&file_path) { Some((i, p)) } else { None })
|
||||
{
|
||||
(*ino, path.clone())
|
||||
} else {
|
||||
let ino = self.len() as u64 + 1;
|
||||
self.insert(ino, file_path);
|
||||
|
||||
(ino, self.get(&ino).unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path(&self, ino: u64) -> Option<&PathBuf> {
|
||||
// TODO CHECK PERM
|
||||
self.get(&ino)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
mod encrypted_filesystem;
|
||||
mod inode_cache;
|
||||
|
||||
pub mod error;
|
||||
|
||||
pub use encrypted_filesystem::EncryptedFs;
|
|
@ -0,0 +1,29 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
#[error(transparent)]
|
||||
ScryptError(#[from] ScryptError),
|
||||
#[error(transparent)]
|
||||
HdkfError(#[from] hkdf::InvalidLength),
|
||||
#[error("Failed to decode base64")]
|
||||
Base64Error(#[from] base64::DecodeError),
|
||||
#[error("Failed to decrypt master key")]
|
||||
MasterKeyDecryptError(),
|
||||
#[error("Invalid master key length")]
|
||||
InvalidMasterKeyLengthError(),
|
||||
}
|
||||
|
||||
impl From<aes_gcm::Error> for ConfigError {
|
||||
fn from(_: aes_gcm::Error) -> Self {
|
||||
Self::MasterKeyDecryptError()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ScryptError {
|
||||
#[error(transparent)]
|
||||
InvalidParams(#[from] scrypt::errors::InvalidParams),
|
||||
#[error(transparent)]
|
||||
InvalidOutputLen(#[from] scrypt::errors::InvalidOutputLen),
|
||||
}
|
|
@ -9,7 +9,9 @@ use aes_gcm::{
|
|||
};
|
||||
use hkdf::Hkdf;
|
||||
|
||||
use crate::error::{FilenameDecryptError, Result, ScryptError};
|
||||
mod error;
|
||||
|
||||
pub use error::*;
|
||||
|
||||
/// An enum that contain all the feature flag a gocryptfs config can have.
|
||||
#[derive(serde::Deserialize, Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -69,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)
|
||||
pub fn get_master_key(&self, password: &[u8]) -> Result<[u8; 32]> {
|
||||
pub fn get_master_key(&self, password: &[u8]) -> Result<[u8; 32], ConfigError> {
|
||||
let block = base64::decode(&self.encrypted_key)?;
|
||||
let key = self.scrypt_object.get_hkdf_key(password)?;
|
||||
|
||||
|
@ -77,12 +79,14 @@ impl CryptConf {
|
|||
let tag = &block[block.len() - 16..];
|
||||
let ciphertext = &block[16..block.len() - 16];
|
||||
|
||||
let mut buf: [u8; 32] = ciphertext.try_into().expect("TODO");
|
||||
let mut buf: [u8; 32] = ciphertext
|
||||
.try_into()
|
||||
.map_err(|_| ConfigError::InvalidMasterKeyLengthError())?;
|
||||
|
||||
let aes = AesGcm::<Aes256, cipher::consts::U16>::new(Key::from_slice(&key));
|
||||
|
||||
aes.decrypt_in_place_detached(
|
||||
GenericArray::from_slice(&nonce),
|
||||
GenericArray::from_slice(nonce),
|
||||
&[0u8, 0, 0, 0, 0, 0, 0, 0],
|
||||
&mut buf,
|
||||
GenericArray::from_slice(tag),
|
||||
|
@ -133,19 +137,14 @@ pub struct ScryptObject {
|
|||
}
|
||||
|
||||
impl ScryptObject {
|
||||
fn get_hkdf_key(&self, password: &[u8]) -> std::result::Result<Vec<u8>, FilenameDecryptError> {
|
||||
fn get_hkdf_key(&self, password: &[u8]) -> Result<Vec<u8>, ConfigError> {
|
||||
let mut key = [0u8; 32];
|
||||
|
||||
let params = scrypt::Params::new((self.n as f64).log2() as u8, self.r, self.p)
|
||||
.map_err(|e| ScryptError::from(e))?;
|
||||
.map_err(ScryptError::from)?;
|
||||
|
||||
scrypt::scrypt(
|
||||
password,
|
||||
&base64::decode(&self.salt).unwrap(),
|
||||
¶ms,
|
||||
&mut key,
|
||||
)
|
||||
.map_err(|e| ScryptError::from(e))?;
|
||||
scrypt::scrypt(password, &base64::decode(&self.salt)?, ¶ms, &mut key)
|
||||
.map_err(ScryptError::from)?;
|
||||
|
||||
let hdkf = Hkdf::<sha2::Sha256>::new(None, &key);
|
||||
hdkf.expand(b"AES-GCM file content encryption", &mut key)?;
|
|
@ -0,0 +1,19 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ContentCipherError {
|
||||
#[error("Block is too short")]
|
||||
BlockTooShort(),
|
||||
#[error("all-zero nonce")]
|
||||
AllZeroNonce(),
|
||||
#[error("Failed to decrypt content")]
|
||||
ContentDecryptError(),
|
||||
#[error(transparent)]
|
||||
HdkfError(#[from] hkdf::InvalidLength),
|
||||
}
|
||||
|
||||
impl From<aes_gcm::Error> for ContentCipherError {
|
||||
fn from(_: aes_gcm::Error) -> Self {
|
||||
Self::ContentDecryptError()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
//! Utilities for file encryption.
|
||||
|
||||
use aes_gcm::{aead::generic_array::GenericArray, aes::Aes256, AeadInPlace, AesGcm, NewAead};
|
||||
use cipher::consts::{U16, U32};
|
||||
use hkdf::Hkdf;
|
||||
|
||||
mod error;
|
||||
|
||||
pub use error::*;
|
||||
|
||||
type Aes256Gcm = AesGcm<Aes256, U16>;
|
||||
|
||||
/// ContentEnc implement all methods related to file encryption.
|
||||
pub struct ContentEnc {
|
||||
key: GenericArray<u8, U32>,
|
||||
iv_len: usize,
|
||||
}
|
||||
|
||||
impl ContentEnc {
|
||||
/// Init a new ContentEnc from the master key and the iv len.
|
||||
pub fn new(master_key: &[u8], iv_len: u8) -> Result<Self, ContentCipherError> {
|
||||
let mut key = [0u8; 32];
|
||||
let hdkf = Hkdf::<sha2::Sha256>::new(None, master_key);
|
||||
hdkf.expand(b"AES-GCM file content encryption", &mut key)?;
|
||||
|
||||
Ok(Self {
|
||||
key: GenericArray::from(key),
|
||||
iv_len: iv_len as usize,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decrypt a encrypted block of len (iv_len + decrypted_block_size + iv_len), with the block number and the file id.
|
||||
pub fn decrypt_block(
|
||||
&self,
|
||||
block: &[u8],
|
||||
block_number: u64,
|
||||
file_id: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, ContentCipherError> {
|
||||
// TODO NOT BOX
|
||||
if block.is_empty() {
|
||||
return Ok(block.into());
|
||||
}
|
||||
|
||||
if block.iter().all(|f| *f == 0) {
|
||||
todo!("black hole")
|
||||
}
|
||||
|
||||
if block.len() < self.iv_len {
|
||||
return Err(ContentCipherError::BlockTooShort());
|
||||
}
|
||||
|
||||
let nonce = &block[..self.iv_len];
|
||||
let tag = &block[block.len() - self.iv_len..];
|
||||
let ciphertext = &block[self.iv_len..block.len() - self.iv_len];
|
||||
|
||||
if nonce.iter().all(|f| *f == 0) {
|
||||
return Err(ContentCipherError::AllZeroNonce());
|
||||
}
|
||||
|
||||
let mut buf = Vec::from(ciphertext);
|
||||
|
||||
let mut aad = Vec::from(block_number.to_be_bytes());
|
||||
if let Some(file_id) = file_id {
|
||||
aad.extend(file_id);
|
||||
}
|
||||
|
||||
let aes = Aes256Gcm::new(&self.key);
|
||||
|
||||
aes.decrypt_in_place_detached(
|
||||
GenericArray::from_slice(nonce),
|
||||
&aad,
|
||||
&mut buf,
|
||||
GenericArray::from_slice(tag),
|
||||
)?;
|
||||
|
||||
Ok(buf.to_vec())
|
||||
}
|
||||
|
||||
/// Return the decrypted size of a file, based on the encrypted size.
|
||||
pub fn get_real_size(encrypted_size: u64) -> u64 {
|
||||
if encrypted_size == 0 {
|
||||
0
|
||||
} else {
|
||||
let x = (encrypted_size - 50) / 4128;
|
||||
|
||||
let y = (encrypted_size - 50) - x * 4128;
|
||||
x * 4096 + y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ContentEnc;
|
||||
|
||||
#[test]
|
||||
fn test_get_real_size() {
|
||||
assert_eq!(0, ContentEnc::get_real_size(0));
|
||||
|
||||
for real_size in 1..4096 * 4 + 1 {
|
||||
let nbr_full_blocks = real_size / 4096;
|
||||
let encrypted_size =
|
||||
18 + nbr_full_blocks * (4096 + 32) + real_size - nbr_full_blocks * 4096 + 32;
|
||||
|
||||
assert_eq!(real_size, ContentEnc::get_real_size(encrypted_size));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
use aes_gcm::{aead::generic_array::GenericArray, aes::Aes256, AeadInPlace, AesGcm, NewAead};
|
||||
use cipher::consts::{U16, U32};
|
||||
use hkdf::Hkdf;
|
||||
|
||||
use crate::error::{Result, DecryptError};
|
||||
|
||||
type Aes256Gcm = AesGcm<Aes256, U16>;
|
||||
|
||||
pub struct ContentEnc {
|
||||
key: GenericArray<u8, U32>,
|
||||
iv_len: usize,
|
||||
}
|
||||
|
||||
impl ContentEnc {
|
||||
pub fn new(master_key: &[u8], iv_len: u8) -> Self {
|
||||
let mut key = [0u8; 32];
|
||||
let hdkf = Hkdf::<sha2::Sha256>::new(None, &master_key);
|
||||
hdkf.expand(b"AES-GCM file content encryption", &mut key)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
key: GenericArray::from(key),
|
||||
iv_len: iv_len as usize,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_block(
|
||||
&self,
|
||||
block: &[u8],
|
||||
block_number: u64,
|
||||
file_id: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>> {
|
||||
// TODO NOT BOX
|
||||
if block.len() == 0 {
|
||||
return Ok(block.into());
|
||||
}
|
||||
|
||||
if block.iter().all(|f| *f == 0) {
|
||||
todo!("black hole")
|
||||
}
|
||||
|
||||
if block.len() < self.iv_len {
|
||||
return Err(DecryptError::BlockTooShort().into());
|
||||
}
|
||||
|
||||
let nonce = &block[..self.iv_len];
|
||||
let tag = &block[block.len() - self.iv_len..];
|
||||
let ciphertext = &block[self.iv_len..block.len() - self.iv_len];
|
||||
|
||||
if nonce.iter().all(|f| *f == 0) {
|
||||
return Err(DecryptError::AllZeroNonce().into());
|
||||
}
|
||||
|
||||
let mut buf = Vec::from(ciphertext);
|
||||
|
||||
let mut aad = Vec::from(block_number.to_be_bytes());
|
||||
if let Some(file_id) = file_id {
|
||||
aad.extend(file_id);
|
||||
}
|
||||
|
||||
let aes = Aes256Gcm::new(&self.key);
|
||||
|
||||
aes.decrypt_in_place_detached(
|
||||
GenericArray::from_slice(nonce),
|
||||
&aad,
|
||||
&mut buf,
|
||||
GenericArray::from_slice(tag),
|
||||
)?;
|
||||
|
||||
return Ok(buf.to_vec());
|
||||
}
|
||||
}
|
|
@ -1,49 +1,20 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::{config::ConfigError, content::ContentCipherError, filename::FilenameCipherError};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// An error that wrap all the errors in this lib.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Failed to decrypt content")]
|
||||
ContentDecryptError(),
|
||||
#[error("Failed to decrypt filename")]
|
||||
FilenameDecryptError(#[from] FilenameDecryptError),
|
||||
#[error("Failed to decode base64")]
|
||||
Base64Error(#[from] base64::DecodeError),
|
||||
#[error(transparent)]
|
||||
DecodeError(#[from] DecryptError),
|
||||
}
|
||||
|
||||
impl From<aes_gcm::Error> for Error {
|
||||
fn from(_: aes_gcm::Error) -> Self {
|
||||
Self::ContentDecryptError()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DecryptError {
|
||||
#[error("Block is too short")]
|
||||
BlockTooShort(),
|
||||
#[error("all-zero nonce")]
|
||||
AllZeroNonce(),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FilenameDecryptError {
|
||||
FilenameCipherError(#[from] FilenameCipherError),
|
||||
#[error(transparent)]
|
||||
ScryptError(#[from] ScryptError),
|
||||
#[error("Failed to decode base64")]
|
||||
Base64Error(#[from] base64::DecodeError),
|
||||
ContentCipherError(#[from] ContentCipherError),
|
||||
#[error(transparent)]
|
||||
HdkfError(#[from] hkdf::InvalidLength),
|
||||
#[error("Failed to decrypt filename")]
|
||||
DecryptError(),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ScryptError {
|
||||
#[error(transparent)]
|
||||
InvalidParams(#[from] scrypt::errors::InvalidParams),
|
||||
#[error(transparent)]
|
||||
InvalidOutputLen(#[from] scrypt::errors::InvalidOutputLen),
|
||||
ConfigError(#[from] ConfigError),
|
||||
#[error(transparent)]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
use cipher::{block_padding::Pkcs7, Iv, Key, KeyIvInit};
|
||||
|
||||
use super::{EmeCipher, EncodedFilename, FilenameCipherError, IntoDecodable};
|
||||
|
||||
/// DirFilenameCipher allow you to cipher and decipher filenames in a directory.
|
||||
///
|
||||
/// TODO : document structure of a gocryptfs dir or put a link.
|
||||
pub struct DirFilenameCipher<'a, 'b> {
|
||||
filename_key: &'a Key<EmeCipher>,
|
||||
iv: &'b Iv<EmeCipher>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> DirFilenameCipher<'a, 'b> {
|
||||
pub fn new(filename_key: &'a Key<EmeCipher>, iv: &'b Iv<EmeCipher>) -> Self {
|
||||
Self { filename_key, iv }
|
||||
}
|
||||
|
||||
/// Decipher a filename.
|
||||
///
|
||||
/// Name muste be the name of the file if it is a short filename, or the content of the long .name file otherwise.
|
||||
pub fn decode_filename<S>(&self, name: S) -> Result<String, FilenameCipherError>
|
||||
where
|
||||
S: IntoDecodable,
|
||||
{
|
||||
let cipher = EmeCipher::new(self.filename_key, self.iv);
|
||||
|
||||
let mut filename = base64::decode_config(name.to_decodable(), base64::URL_SAFE_NO_PAD)?;
|
||||
let filename_decoded = cipher
|
||||
.decrypt_padded_mut::<Pkcs7>(&mut filename)
|
||||
.map_err(|_| FilenameCipherError::DecryptError())?;
|
||||
|
||||
Ok(String::from_utf8_lossy(filename_decoded).to_string())
|
||||
}
|
||||
|
||||
/// Cipher a filename.
|
||||
pub fn encrypt_filename(
|
||||
&self,
|
||||
plain_text_name: &str,
|
||||
) -> Result<EncodedFilename, FilenameCipherError> {
|
||||
let mut cipher = EmeCipher::new(self.filename_key, self.iv);
|
||||
let mut res = [0u8; 2048];
|
||||
|
||||
let filename_encrypted = cipher
|
||||
.encrypt_padded_b2b_mut::<Pkcs7>(plain_text_name.as_bytes(), &mut res)
|
||||
.map_err(|_| FilenameCipherError::EncryptError())?;
|
||||
|
||||
let filename = base64::encode_config(filename_encrypted, base64::URL_SAFE_NO_PAD);
|
||||
|
||||
Ok(filename.into())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FilenameCipherError {
|
||||
#[error("Failed to decode base64")]
|
||||
Base64Error(#[from] base64::DecodeError),
|
||||
#[error(transparent)]
|
||||
HdkfError(#[from] hkdf::InvalidLength),
|
||||
#[error("Failed to decrypt filename")]
|
||||
DecryptError(),
|
||||
#[error("Failed to encrypt filename")]
|
||||
EncryptError()
|
||||
}
|
|
@ -1,49 +1,44 @@
|
|||
use std::path::Path;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
/// EncodedFilename
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Represent an encrypted filename.
|
||||
///
|
||||
/// An encrypted filename can have two forms : long or short.
|
||||
/// TODO: Document
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum EncodedFilename {
|
||||
ShortFilename(String),
|
||||
LongFilename(LongFilename),
|
||||
}
|
||||
|
||||
impl EncodedFilename {
|
||||
fn new<P>(file: P) -> crate::error::Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = file.as_ref();
|
||||
|
||||
let filename = path.file_name().unwrap().to_str().expect("Failed to get filename");
|
||||
|
||||
if filename.starts_with("gocryptfs.longname.") {
|
||||
if !filename.ends_with(".name") {
|
||||
let long = std::fs::read_to_string(
|
||||
path.parent().unwrap().join(format!("{}.name", filename)),
|
||||
).unwrap();
|
||||
Ok(EncodedFilename::LongFilename(LongFilename {
|
||||
filename: filename.to_string(),
|
||||
filename_content: long,
|
||||
}))
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
} else {
|
||||
Ok(EncodedFilename::ShortFilename(filename.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct LongFilename {
|
||||
pub filename: String,
|
||||
pub filename_content: String,
|
||||
filename: String,
|
||||
filename_content: String,
|
||||
}
|
||||
|
||||
impl LongFilename {
|
||||
pub fn filename(&self) -> &str {
|
||||
self.filename.as_ref()
|
||||
}
|
||||
|
||||
pub fn filename_content(&self) -> &str {
|
||||
self.filename_content.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for EncodedFilename {
|
||||
fn from(filename: String) -> Self {
|
||||
if filename.len() > 255 {
|
||||
unimplemented!()
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(filename.as_bytes());
|
||||
|
||||
Self::LongFilename(LongFilename {
|
||||
filename: format!(
|
||||
"gocryptfs.longname.{}",
|
||||
base64::encode_config(hasher.finalize(), base64::URL_SAFE_NO_PAD)
|
||||
),
|
||||
filename_content: filename,
|
||||
})
|
||||
} else {
|
||||
Self::ShortFilename(filename)
|
||||
}
|
||||
|
@ -51,11 +46,11 @@ impl From<String> for EncodedFilename {
|
|||
}
|
||||
|
||||
pub trait IntoDecodable {
|
||||
fn to_decodable<'s>(&'s self) -> &'s str;
|
||||
fn to_decodable(&self) -> &str;
|
||||
}
|
||||
|
||||
impl IntoDecodable for EncodedFilename {
|
||||
fn to_decodable<'s>(&'s self) -> &'s str {
|
||||
fn to_decodable(&self) -> &str {
|
||||
match self {
|
||||
Self::ShortFilename(s) => s.as_str(),
|
||||
Self::LongFilename(l) => l.filename_content.as_str(),
|
||||
|
@ -64,13 +59,13 @@ impl IntoDecodable for EncodedFilename {
|
|||
}
|
||||
|
||||
impl IntoDecodable for String {
|
||||
fn to_decodable<'s>(&'s self) -> &'s str {
|
||||
fn to_decodable(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDecodable for &'a str {
|
||||
fn to_decodable<'s>(&'s self) -> &'s str {
|
||||
fn to_decodable(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
//! Utilities for filename encryption.
|
||||
//!
|
||||
use aes::Aes256;
|
||||
use cipher::{block_padding::Pkcs7, inout::InOutBufReserved, Iv, Key, KeyIvInit};
|
||||
use cipher::{Iv, Key};
|
||||
use eme_mode::DynamicEme;
|
||||
use hkdf::Hkdf;
|
||||
|
||||
use crate::error::FilenameDecryptError;
|
||||
|
||||
pub(crate) type EmeCipher = DynamicEme<Aes256>;
|
||||
|
||||
mod dir_filename_cipher;
|
||||
mod error;
|
||||
mod filename_encoded;
|
||||
|
||||
pub use dir_filename_cipher::*;
|
||||
pub use error::*;
|
||||
pub use filename_encoded::*;
|
||||
|
||||
// TODO RENAME
|
||||
pub struct FilenameDecoder {
|
||||
/// FilenameCipher allow you to retrieve a DirFilenameCipher, used to cipher and decipher filenames.
|
||||
pub struct FilenameCipher {
|
||||
filename_key: Key<Aes256>,
|
||||
}
|
||||
|
||||
impl FilenameDecoder {
|
||||
pub fn new(master_key: &[u8]) -> Result<Self, FilenameDecryptError> {
|
||||
impl FilenameCipher {
|
||||
/// Create a new FilenameCipher, from the master key.
|
||||
pub fn new(master_key: &[u8]) -> Result<Self, FilenameCipherError> {
|
||||
let mut key = [0u8; 32];
|
||||
let hdkf = Hkdf::<sha2::Sha256>::new(None, &master_key);
|
||||
let hdkf = Hkdf::<sha2::Sha256>::new(None, master_key);
|
||||
hdkf.expand(b"EME filename encryption", &mut key)?;
|
||||
|
||||
Ok(Self {
|
||||
|
@ -27,54 +32,10 @@ impl FilenameDecoder {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_decoder_for_dir<'a, 'b>(&'a self, iv: &'b [u8]) -> DirFilenameDecoder<'a, 'b> {
|
||||
/// Get the cipher for a directory, allowing you to decipher files in this dir.
|
||||
pub fn get_cipher_for_dir<'a, 'b>(&'a self, iv: &'b [u8]) -> DirFilenameCipher<'a, 'b> {
|
||||
let iv = Iv::<EmeCipher>::from_slice(iv);
|
||||
DirFilenameDecoder {
|
||||
filename_key: &self.filename_key,
|
||||
iv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO RENAME
|
||||
pub struct DirFilenameDecoder<'a, 'b> {
|
||||
filename_key: &'a Key<EmeCipher>,
|
||||
iv: &'b Iv<EmeCipher>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> DirFilenameDecoder<'a, 'b> {
|
||||
pub fn decode_filename<S>(&self, name: S) -> Result<String, FilenameDecryptError>
|
||||
where
|
||||
S: IntoDecodable,
|
||||
{
|
||||
let cipher = EmeCipher::new(self.filename_key, self.iv);
|
||||
|
||||
let mut filename = base64::decode_config(name.to_decodable(), base64::URL_SAFE_NO_PAD)?;
|
||||
let filename_decoded = cipher
|
||||
.decrypt_padded_mut::<Pkcs7>(&mut filename)
|
||||
.map_err(|_| FilenameDecryptError::DecryptError())?;
|
||||
|
||||
Ok(String::from_utf8_lossy(filename_decoded).to_string())
|
||||
}
|
||||
|
||||
pub fn encrypt_filename(
|
||||
&self,
|
||||
plain_text_name: &str,
|
||||
) -> Result<EncodedFilename, FilenameDecryptError> {
|
||||
let mut cipher = EmeCipher::new(self.filename_key, self.iv);
|
||||
let mut res = [0u8; 2048];
|
||||
|
||||
let filename_encrypted = cipher
|
||||
.encrypt_padded_inout_mut::<Pkcs7>(
|
||||
InOutBufReserved::from_slices(plain_text_name.as_bytes(), &mut res).unwrap(),
|
||||
)
|
||||
.map_err(|_| FilenameDecryptError::DecryptError())?; // TODO RENAME ERROR
|
||||
|
||||
// TODO LONG FILENAME
|
||||
|
||||
let filename = base64::encode_config(filename_encrypted, base64::URL_SAFE_NO_PAD);
|
||||
|
||||
Ok(filename.into())
|
||||
DirFilenameCipher::new(&self.filename_key, iv)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,15 +43,15 @@ impl<'a, 'b> DirFilenameDecoder<'a, 'b> {
|
|||
mod test {
|
||||
use crate::filename::EncodedFilename;
|
||||
|
||||
use super::FilenameDecoder;
|
||||
use super::FilenameCipher;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt() {
|
||||
let master_key = base64::decode("9gtUW9XiiefEgEXEkbONI6rnUsd2yh5UZZLG0V8Bxgk=").unwrap();
|
||||
let dir_iv = base64::decode("6ysCeWOp2euF1x39gth8KQ==").unwrap();
|
||||
|
||||
let decoder = FilenameDecoder::new(&master_key).expect("Failed to get file decoder");
|
||||
let dir_decoder = decoder.get_decoder_for_dir(&dir_iv);
|
||||
let decoder = FilenameCipher::new(&master_key).expect("Failed to get file decoder");
|
||||
let dir_decoder = decoder.get_cipher_for_dir(&dir_iv);
|
||||
|
||||
let encoded = dir_decoder
|
||||
.encrypt_filename("7.mp4")
|
||||
|
@ -107,8 +68,8 @@ mod test {
|
|||
let master_key = base64::decode("9gtUW9XiiefEgEXEkbONI6rnUsd2yh5UZZLG0V8Bxgk=").unwrap();
|
||||
let dir_iv = base64::decode("6ysCeWOp2euF1x39gth8KQ==").unwrap();
|
||||
|
||||
let decoder = FilenameDecoder::new(&master_key).expect("Failed to get file decoder");
|
||||
let dir_decoder = decoder.get_decoder_for_dir(&dir_iv);
|
||||
let decoder = FilenameCipher::new(&master_key).expect("Failed to get file decoder");
|
||||
let dir_decoder = decoder.get_cipher_for_dir(&dir_iv);
|
||||
|
||||
let decrypted = dir_decoder
|
||||
.decode_filename("vTBajRt-yCpxB7Sly0E7lQ")
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
use std::{
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::filename::{EncodedFilename, FilenameDecoder};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirCache {
|
||||
filename: String,
|
||||
dir_iv: [u8; 16],
|
||||
dir_entries: Vec<DirEntry>,
|
||||
}
|
||||
|
||||
impl DirCache {
|
||||
pub fn load_from_path<P>(path: P) -> Self
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
|
||||
let mut dir_iv = [0u8; 16];
|
||||
{
|
||||
let dir_iv_path = path.join("gocryptfs.diriv");
|
||||
|
||||
let mut file = File::open(dir_iv_path).unwrap();
|
||||
|
||||
file.read_exact(&mut dir_iv).unwrap();
|
||||
}
|
||||
|
||||
let dir_entries = path
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.filter_map(|f| {
|
||||
if let Ok(entry) = f {
|
||||
if entry.file_name() != "gocryptfs.conf"
|
||||
&& entry.file_name() != "gocryptfs.diriv"
|
||||
{
|
||||
Some(entry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|f| DirEntry::try_from(f.path().as_path()))
|
||||
.filter_map(|f| f.ok())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
dir_iv,
|
||||
dir_entries,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup<P>(
|
||||
&self,
|
||||
filename_decoder: &FilenameDecoder,
|
||||
decrypted_path: P,
|
||||
) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let decrypted_path = decrypted_path.as_ref();
|
||||
|
||||
let mut components = decrypted_path.components();
|
||||
|
||||
let component = components.next().expect("lol");
|
||||
|
||||
let decoder = filename_decoder.get_decoder_for_dir(&self.dir_iv);
|
||||
|
||||
let segment = decoder
|
||||
.encrypt_filename(component.as_os_str().to_str().unwrap())
|
||||
.expect("lol");
|
||||
|
||||
let segment_path = match segment {
|
||||
EncodedFilename::ShortFilename(filename) => PathBuf::from(filename),
|
||||
EncodedFilename::LongFilename(long_filename) => PathBuf::from(long_filename.filename),
|
||||
};
|
||||
|
||||
if segment_path.is_dir() {
|
||||
let (size, _) = components.size_hint();
|
||||
|
||||
if size > 0 {
|
||||
unimplemented!()
|
||||
} else {
|
||||
unimplemented!()
|
||||
//None
|
||||
}
|
||||
} else {
|
||||
// component.as_path()
|
||||
unimplemented!()
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
fn lookup_internal<P>(
|
||||
&self,
|
||||
filename_decoder: &FilenameDecoder,
|
||||
decrypted_path: P,
|
||||
dir: &DirCache,
|
||||
) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DirEntry {
|
||||
Dir(DirCache),
|
||||
File(String),
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
/// Returns `true` if the dir entry is [`Dir`].
|
||||
///
|
||||
/// [`Dir`]: DirEntry::Dir
|
||||
#[must_use]
|
||||
pub fn is_dir(&self) -> bool {
|
||||
matches!(self, Self::Dir(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if the dir entry is [`File`].
|
||||
///
|
||||
/// [`File`]: DirEntry::File
|
||||
#[must_use]
|
||||
pub fn is_file(&self) -> bool {
|
||||
matches!(self, Self::File(..))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for DirEntry {
|
||||
type Error = Box<dyn Error>; // TODO
|
||||
|
||||
fn try_from(path: &Path) -> Result<Self, Self::Error> {
|
||||
Ok(if path.is_dir() {
|
||||
DirEntry::Dir(DirCache::load_from_path(path))
|
||||
} else {
|
||||
DirEntry::File(
|
||||
path.components()
|
||||
.last()
|
||||
.unwrap()
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
//! A library to write gocryptfs compatible programs
|
||||
//! A library to write gocryptfs compatible programs.
|
||||
|
||||
use std::{fs::File, path::Path};
|
||||
use std::{fs::File, io::Read, path::Path};
|
||||
|
||||
use content_enc::ContentEnc;
|
||||
use filename::FilenameDecoder;
|
||||
use content::ContentEnc;
|
||||
use filename::FilenameCipher;
|
||||
|
||||
pub mod config;
|
||||
pub mod content_enc;
|
||||
pub mod content;
|
||||
pub mod error;
|
||||
pub mod filename;
|
||||
|
||||
/// A GocryptFs encrypted directory
|
||||
pub struct GocryptFs {
|
||||
filename_decoder: FilenameDecoder,
|
||||
filename_decoder: FilenameCipher,
|
||||
content_decoder: ContentEnc,
|
||||
}
|
||||
|
||||
|
@ -26,18 +26,25 @@ impl GocryptFs {
|
|||
{
|
||||
let base_path = encrypted_dir_path.as_ref();
|
||||
|
||||
let config = {
|
||||
let mut config_file =
|
||||
File::open(base_path.join("gocryptfs.conf")).expect("failed to get config");
|
||||
File::open(base_path.join("gocryptfs.conf"))?;
|
||||
|
||||
serde_json::from_reader::<_, config::CryptConf>(&mut config_file)
|
||||
.expect("failed to parse config")
|
||||
};
|
||||
Self::load_from_reader(&mut config_file, password.as_bytes())
|
||||
}
|
||||
|
||||
let master_key = config.get_master_key(password.as_bytes())?;
|
||||
/// Load a gocryptfs from the config.
|
||||
///
|
||||
/// reader_config must be a reader of a valid `gocryptfs.conf`.
|
||||
pub fn load_from_reader<R>(reader_config: &mut R, password: &[u8]) -> error::Result<Self>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
let config = serde_json::from_reader::<_, config::CryptConf>(reader_config)?;
|
||||
|
||||
let filename_decoder = FilenameDecoder::new(&master_key)?;
|
||||
let content_decoder = ContentEnc::new(&master_key, 16); // TODO IV LEN
|
||||
let master_key = config.get_master_key(password)?;
|
||||
|
||||
let filename_decoder = FilenameCipher::new(&master_key)?;
|
||||
let content_decoder = ContentEnc::new(&master_key, 16)?; // TODO IV LEN
|
||||
|
||||
Ok(Self {
|
||||
filename_decoder,
|
||||
|
@ -45,10 +52,12 @@ impl GocryptFs {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn filename_decoder(&self) -> &FilenameDecoder {
|
||||
/// Get the [`filename decoder`](struct@FilenameCipher) attached to this GocryptFs.
|
||||
pub fn filename_decoder(&self) -> &FilenameCipher {
|
||||
&self.filename_decoder
|
||||
}
|
||||
|
||||
/// Get the [`content decoder`](struct@ContentEnc) attached to this GocryptFs.
|
||||
pub fn content_decoder(&self) -> &ContentEnc {
|
||||
&self.content_decoder
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "rustcryptfs-mount"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
rustcryptfs-fuse = { path = "../rustcryptfs-fuse" }
|
|
@ -0,0 +1,14 @@
|
|||
use std::path::Path;
|
||||
|
||||
use rustcryptfs_fuse::EncryptedFs;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn mount<P>(path: P, mount_point: P, password: &str) -> rustcryptfs_fuse::error::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let fs = EncryptedFs::new(path, password)?;
|
||||
|
||||
fs.mount(mount_point)?;
|
||||
Ok(())
|
||||
}
|
|
@ -5,6 +5,10 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["mount"]
|
||||
mount = ["rustcryptfs-mount"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.53"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
|
@ -13,3 +17,5 @@ clap = { version = "3.1.18", features = ["derive"] }
|
|||
log = "0.4.17"
|
||||
rustcryptfs-lib = { path = "../rustcryptfs-lib" }
|
||||
env_logger = "0.9.0"
|
||||
rpassword = "7.0.0"
|
||||
rustcryptfs-mount = { path = "../rustcryptfs-mount", optional = true }
|
|
@ -1,3 +1,6 @@
|
|||
#[cfg(feature = "mount")]
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
|
@ -12,8 +15,12 @@ pub(crate) enum Commands {
|
|||
/// Decrypt a file
|
||||
Decrypt(DecryptCommand),
|
||||
|
||||
// List file contained in a directory
|
||||
/// List file contained in a directory
|
||||
Ls(LsCommand),
|
||||
|
||||
#[cfg(feature = "mount")]
|
||||
/// Mount an encrypted folder
|
||||
Mount(MountCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
|
@ -43,3 +50,17 @@ pub(crate) struct LsCommand {
|
|||
#[clap(short, long)]
|
||||
pub(crate) password : Option<String>
|
||||
}
|
||||
|
||||
#[cfg(feature = "mount")]
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct MountCommand {
|
||||
/// The directory
|
||||
pub(crate) path: PathBuf,
|
||||
|
||||
/// The mount point
|
||||
pub(crate) mountpoint: PathBuf,
|
||||
|
||||
/// The password
|
||||
#[clap(short, long)]
|
||||
pub(crate) password: Option<String>,
|
||||
}
|
||||
|
|
|
@ -9,39 +9,49 @@ use clap::Parser;
|
|||
use args::{DecryptCommand, LsCommand};
|
||||
use rustcryptfs_lib::GocryptFs;
|
||||
|
||||
#[cfg(feature = "mount")]
|
||||
use args::MountCommand;
|
||||
|
||||
mod args;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
env_logger::init();
|
||||
let args = args::Args::parse();
|
||||
log::debug!("{:?}", args);
|
||||
|
||||
match &args.command {
|
||||
args::Commands::Decrypt(c) => decrypt_file(c),
|
||||
args::Commands::Ls(c) => ls(c),
|
||||
#[cfg(feature = "mount")]
|
||||
args::Commands::Mount(c) => mount(c),
|
||||
}
|
||||
}
|
||||
|
||||
fn ls(c: &LsCommand) -> anyhow::Result<()> {
|
||||
let folder_path = Path::new(&c.folder_path);
|
||||
|
||||
let password = if let Some(password) = &c.password {
|
||||
password.clone()
|
||||
} else {
|
||||
rpassword::prompt_password("Your password: ")?
|
||||
};
|
||||
|
||||
let fs = GocryptFs::open(
|
||||
c.gocryptfs_path
|
||||
.as_ref()
|
||||
.map(|p| Path::new(p))
|
||||
.map(Path::new)
|
||||
.unwrap_or(folder_path),
|
||||
c.password.as_ref().expect("Please input a password"),
|
||||
&password,
|
||||
)?;
|
||||
|
||||
let filename_decoder = fs.filename_decoder();
|
||||
|
||||
let iv = std::fs::read(folder_path.join("gocryptfs.diriv"))?;
|
||||
|
||||
let dir_decoder = filename_decoder.get_decoder_for_dir(&iv);
|
||||
let dir_decoder = filename_decoder.get_cipher_for_dir(&iv);
|
||||
|
||||
for dir in std::fs::read_dir(folder_path)?.flat_map(|e| e.ok()) {
|
||||
let filename = dir.file_name();
|
||||
let filename = filename.to_str().unwrap();
|
||||
let filename = filename.to_string_lossy();
|
||||
|
||||
if filename != "gocryptfs.conf" && filename != "gocryptfs.diriv" {
|
||||
if filename.starts_with("gocryptfs.longname.") {
|
||||
|
@ -52,28 +62,33 @@ fn ls(c: &LsCommand) -> anyhow::Result<()> {
|
|||
println!("{}", res);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Ok(res) = dir_decoder.decode_filename(filename) {
|
||||
} else if let Ok(res) = dir_decoder.decode_filename(&*filename) {
|
||||
println!("{}", res);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_file(c: &DecryptCommand) -> anyhow::Result<()> {
|
||||
let file_path = Path::new(&c.file_path);
|
||||
|
||||
let password = if let Some(password) = &c.password {
|
||||
password.clone()
|
||||
} else {
|
||||
rpassword::prompt_password("Your password: ")?
|
||||
};
|
||||
|
||||
let fs = GocryptFs::open(
|
||||
c.gocryptfs_path
|
||||
.as_ref()
|
||||
.map(|p| Path::new(p))
|
||||
.map(Path::new)
|
||||
.unwrap_or_else(|| file_path.parent().unwrap()),
|
||||
c.password.as_ref().expect("Please input a password"),
|
||||
&password,
|
||||
)?;
|
||||
|
||||
let mut file = File::open(file_path).unwrap();
|
||||
let mut file = File::open(file_path)?;
|
||||
|
||||
let enc = fs.content_decoder();
|
||||
|
||||
|
@ -92,7 +107,7 @@ fn decrypt_file(c: &DecryptCommand) -> anyhow::Result<()> {
|
|||
|
||||
stdout.write_all(&res)?;
|
||||
|
||||
if res.len() == 0 {
|
||||
if res.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -102,3 +117,26 @@ fn decrypt_file(c: &DecryptCommand) -> anyhow::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(feature = "mount")]
|
||||
fn mount(mount: &MountCommand) -> anyhow::Result<()> {
|
||||
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 fuse fs")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[cfg(feature = "mount")]
|
||||
fn mount(mount: &MountCommand) -> anyhow::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue