Compare commits
19 Commits
75cace0568
...
eb42e54182
Author | SHA1 | Date |
---|---|---|
Jakob Unterwurzacher | eb42e54182 | |
Jakob Unterwurzacher | a1f01419e2 | |
Jakob Unterwurzacher | bd1ecf5379 | |
Jakob Unterwurzacher | a48d6c3041 | |
Jakob Unterwurzacher | ec186c13ce | |
Jakob Unterwurzacher | 7d60315cd5 | |
Jakob Unterwurzacher | de22cb1e5d | |
Jakob Unterwurzacher | 39e736c099 | |
Jakob Unterwurzacher | 8722b894a6 | |
Jakob Unterwurzacher | d530fbd400 | |
Jakob Unterwurzacher | 87a6bb370a | |
Jakob Unterwurzacher | d14c9340d6 | |
Jakob Unterwurzacher | d583bdb79e | |
Jakob Unterwurzacher | dc32710045 | |
Jakob Unterwurzacher | a652be805e | |
Jakob Unterwurzacher | 4ba0ced3c7 | |
Jakob Unterwurzacher | b0bddc5ed0 | |
Charles Duffy | 8ec872e330 | |
Jakob Unterwurzacher | 3b881b0174 |
|
@ -36,6 +36,9 @@ jobs:
|
|||
# CI platform specific setup steps happen here
|
||||
- run: sudo apt-get install -qq fuse3 libssl-dev
|
||||
|
||||
# Fix "/usr/bin/fusermount: option allow_other only allowed if 'user_allow_other' is set in /etc/fuse.conf"
|
||||
- run: echo user_allow_other | sudo tee -a /etc/fuse.conf
|
||||
|
||||
# Build & upload static binary
|
||||
- run: ./build-without-openssl.bash
|
||||
- uses: actions/upload-artifact@v2
|
||||
|
|
|
@ -123,6 +123,28 @@ and https://github.com/rfjakob/gocryptfs/issues/596 for background info.
|
|||
Use HKDF to derive separate keys for content and name encryption from
|
||||
the master key. Default true.
|
||||
|
||||
#### -longnamemax
|
||||
|
||||
integer value, allowed range 62...255
|
||||
|
||||
Hash file names that (in encrypted form) exceed this length. The default
|
||||
is 255, which aligns with the usual name length limit on Linux and
|
||||
provides best performance.
|
||||
|
||||
However, online storage may impose lower limits on file name and/or
|
||||
path length. In this case, setting -longnamemax to a lower value
|
||||
can be helpful.
|
||||
|
||||
The lower the value, the more extra `.name` files
|
||||
must be created, which slows down directory listings.
|
||||
|
||||
Values below 62 are not allowed as then the hashed name
|
||||
would be longer than the original name.
|
||||
|
||||
Example:
|
||||
|
||||
-longnamemax 100
|
||||
|
||||
#### -plaintextnames
|
||||
Do not encrypt file names and symlink targets.
|
||||
|
||||
|
@ -296,9 +318,10 @@ the directories. Example:
|
|||
gocryptfs -ko noexec /tmp/foo /tmp/bar
|
||||
|
||||
#### -longnames
|
||||
Store names longer than 176 bytes in extra files (default true)
|
||||
This flag is useful when recovering old gocryptfs filesystems using
|
||||
"-masterkey". It is ignored (stays at the default) otherwise.
|
||||
Store names that are longer than 175 bytes in extra files (default true).
|
||||
|
||||
This flag is only useful when recovering very old gocryptfs filesystems (gocryptfs v0.8 and earlier)
|
||||
using "-masterkey". It is ignored (stays at the default) otherwise.
|
||||
|
||||
#### -nodev
|
||||
See `-dev, -nodev`.
|
||||
|
@ -452,6 +475,10 @@ to your program, use `"--"`, which is accepted by most programs:
|
|||
|
||||
Applies to: all actions that ask for a password.
|
||||
|
||||
BUG: In `-extpass -X`, the `-X` will be interpreted as `--X`. Please use
|
||||
`-extpass=-X` to prevent that. See **Dash duplication** in the **BUGS** section
|
||||
for details.
|
||||
|
||||
#### -fido2 DEVICE_PATH
|
||||
Use a FIDO2 token to initialize and unlock the filesystem.
|
||||
Use "fido2-token -L" to obtain the FIDO2 token device path.
|
||||
|
@ -646,6 +673,13 @@ the `-nofail` option for details).
|
|||
|
||||
/tmp/cipher /tmp/plain fuse./usr/local/bin/gocryptfs nofail,allow_other,passfile=/tmp/password 0 0
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
=====================
|
||||
|
||||
### NO_COLOR
|
||||
|
||||
If `NO_COLOR` is set (regardless of value), colored output is disabled (see https://no-color.org/).
|
||||
|
||||
EXIT CODES
|
||||
==========
|
||||
|
||||
|
@ -661,6 +695,29 @@ other: please check the error message
|
|||
|
||||
See also: https://github.com/rfjakob/gocryptfs/blob/master/internal/exitcodes/exitcodes.go
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
### Dash duplication
|
||||
|
||||
gocryptfs v2.1 switched to the `pflag` library for command-line parsing
|
||||
to support flags and positional arguments in any order. To stay compatible
|
||||
with single-dash long options like `-extpass`, an ugly hack was added:
|
||||
The command line is preprocessed, and all single-dash options are converted to
|
||||
double-dash.
|
||||
|
||||
Unfortunately, this means that in
|
||||
|
||||
gocryptfs -extpass myapp -extpass -X
|
||||
|
||||
gocryptfs transforms the `-X` to `--X`, and it will call `myapp --X` as the extpass program.
|
||||
|
||||
Please use
|
||||
|
||||
gocryptfs -extpass myapp -extpass=-X
|
||||
|
||||
to work around this bug.
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
mount(2) fuse(8) fallocate(2) encfs(1) gitignore(5)
|
||||
|
|
14
README.md
14
README.md
|
@ -196,6 +196,20 @@ RM: 2,367
|
|||
Changelog
|
||||
---------
|
||||
|
||||
#### vNEXT
|
||||
* Add **`-longnamemax`** flag to `-init` ([#499](https://github.com/rfjakob/gocryptfs/issues/499)).
|
||||
Can be used to work around file or path length restrictions on online storage.
|
||||
See the [man page](https://github.com/rfjakob/gocryptfs/blob/master/Documentation/MANPAGE.md#-longnamemax)
|
||||
for details.
|
||||
|
||||
#### v2.2.1, 2021-10-20
|
||||
* Fix `-force_owner` only taking effect after 2 seconds ([#609](https://github.com/rfjakob/gocryptfs/issues/609)).
|
||||
This was a regression introduced in v2.0.
|
||||
* MacOS: Fix build.bash failure with error `date: illegal option -- -` when `SOURCE_DATE_EPOCH` is set
|
||||
([#570](https://github.com/rfjakob/gocryptfs/issues/570))
|
||||
* `-init`: suggest xchacha on CPUs without AES acceleration ([commit](https://github.com/rfjakob/gocryptfs/commit/e8e35982845f36e714b915350eaf6855487aa0e8))
|
||||
* `-info`: add contentEncryption to output
|
||||
|
||||
#### v2.2.0, 2021-09-25
|
||||
* **`-deterministic-names`: new option for `-init`**, both for reverse and forward mode.
|
||||
Disables file name randomisation & `gocryptfs.diriv` files
|
||||
|
|
14
cli_args.go
14
cli_args.go
|
@ -45,6 +45,8 @@ type argContainer struct {
|
|||
notifypid, scryptn int
|
||||
// Idle time before autounmount
|
||||
idle time.Duration
|
||||
// -longnamemax (hash encrypted names that are longer than this)
|
||||
longnamemax uint8
|
||||
// Helper variables that are NOT cli options all start with an underscore
|
||||
// _configCustom is true when the user sets a custom config file name.
|
||||
_configCustom bool
|
||||
|
@ -112,6 +114,10 @@ func prefixOArgs(osArgs []string) ([]string, error) {
|
|||
// into "--debug" (spf13/pflag style).
|
||||
// gocryptfs v2.1 switched from `flag` to `pflag`, but we obviously want to stay
|
||||
// cli-compatible, and this is the hack to do it.
|
||||
//
|
||||
// BUG: In `-extpass -X`, the `-X` gets transformed `--X`.
|
||||
// See "Dash duplication" in the man page and
|
||||
// https://github.com/rfjakob/gocryptfs/issues/621 .
|
||||
func convertToDoubleDash(osArgs []string) (out []string) {
|
||||
out = append(out, osArgs...)
|
||||
for i, v := range out {
|
||||
|
@ -161,7 +167,7 @@ func parseCliOpts(osArgs []string) (args argContainer) {
|
|||
flagSet.BoolVar(&args.quiet, "quiet", false, "Quiet - silence informational messages")
|
||||
flagSet.BoolVar(&args.nosyslog, "nosyslog", false, "Do not redirect output to syslog when running in the background")
|
||||
flagSet.BoolVar(&args.wpanic, "wpanic", false, "When encountering a warning, panic and exit immediately")
|
||||
flagSet.BoolVar(&args.longnames, "longnames", true, "Store names longer than 176 bytes in extra files")
|
||||
flagSet.BoolVar(&args.longnames, "longnames", true, "Store names longer than 175 bytes in extra files")
|
||||
flagSet.BoolVar(&args.allow_other, "allow_other", false, "Allow other users to access the filesystem. "+
|
||||
"Only works if user_allow_other is set in /etc/fuse.conf.")
|
||||
flagSet.BoolVar(&args.reverse, "reverse", false, "Reverse mode")
|
||||
|
@ -215,6 +221,8 @@ func parseCliOpts(osArgs []string) (args argContainer) {
|
|||
flagSet.StringSliceVar(&args.badname, "badname", nil, "Glob pattern invalid file names that should be shown")
|
||||
flagSet.StringSliceVar(&args.passfile, "passfile", nil, "Read password from file")
|
||||
|
||||
flagSet.Uint8Var(&args.longnamemax, "longnamemax", 255, "Hash encrypted names that are longer than this")
|
||||
|
||||
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
|
||||
"successful mount - used internally for daemonization")
|
||||
const scryptn = "scryptn"
|
||||
|
@ -292,6 +300,10 @@ func parseCliOpts(osArgs []string) (args argContainer) {
|
|||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
}
|
||||
if args.longnamemax > 0 && args.longnamemax < 62 {
|
||||
tlog.Fatal.Printf("-longnamemax: value %d is outside allowed range 62 ... 255", args.longnamemax)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
|
|
@ -116,11 +116,12 @@ func TestConvertToDoubleDash(t *testing.T) {
|
|||
|
||||
func TestParseCliOpts(t *testing.T) {
|
||||
defaultArgs := argContainer{
|
||||
longnames: true,
|
||||
raw64: true,
|
||||
hkdf: true,
|
||||
openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
|
||||
scryptn: 16,
|
||||
longnames: true,
|
||||
longnamemax: 255,
|
||||
raw64: true,
|
||||
hkdf: true,
|
||||
openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
|
||||
scryptn: 16,
|
||||
}
|
||||
|
||||
type testcaseContainer struct {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//+build linux
|
||||
|
||||
/*
|
||||
Small tool to try to debug unix.Getdents problems on CIFS mounts
|
||||
( https://github.com/rfjakob/gocryptfs/issues/483 )
|
||||
|
|
|
@ -1,29 +1,37 @@
|
|||
#!/bin/bash -eu
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build on all supported architectures & operating systems
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
function build {
|
||||
# Discard resulting binary by writing to /dev/null
|
||||
go build -tags without_openssl -o /dev/null
|
||||
}
|
||||
|
||||
function compile_tests {
|
||||
for i in $(go list ./...) ; do
|
||||
go test -c -tags without_openssl -o /dev/null "$i" > /dev/null
|
||||
done
|
||||
}
|
||||
|
||||
set -eux
|
||||
|
||||
export GO111MODULE=on
|
||||
# Discard resulting binary by writing to /dev/null
|
||||
B="go build -tags without_openssl -o /dev/null"
|
||||
|
||||
set -x
|
||||
|
||||
export CGO_ENABLED=0
|
||||
|
||||
GOOS=linux GOARCH=amd64 $B
|
||||
GOOS=linux GOARCH=amd64 build
|
||||
|
||||
# See https://github.com/golang/go/wiki/GoArm
|
||||
GOOS=linux GOARCH=arm GOARM=7 $B
|
||||
GOOS=linux GOARCH=arm64 $B
|
||||
GOOS=linux GOARCH=arm GOARM=7 build
|
||||
GOOS=linux GOARCH=arm64 build
|
||||
|
||||
# MacOS on Intel
|
||||
GOOS=darwin GOARCH=amd64 $B
|
||||
GOOS=darwin GOARCH=amd64 build
|
||||
# Catch tests that don't work on MacOS (takes a long time so we only run it once)
|
||||
time GOOS=darwin GOARCH=amd64 compile_tests
|
||||
|
||||
# MacOS on Apple Silicon M1.
|
||||
# Go 1.16 added support for the M1 and added ios/arm64,
|
||||
# so we use this to check if we should attempt a build.
|
||||
if go tool dist list | grep ios/arm64 ; then
|
||||
GOOS=darwin GOARCH=arm64 $B
|
||||
GOOS=darwin GOARCH=arm64 build
|
||||
fi
|
||||
|
|
|
@ -102,7 +102,9 @@ func initDir(args *argContainer) {
|
|||
Fido2CredentialID: fido2CredentialID,
|
||||
Fido2HmacSalt: fido2HmacSalt,
|
||||
DeterministicNames: args.deterministic_names,
|
||||
XChaCha20Poly1305: args.xchacha})
|
||||
XChaCha20Poly1305: args.xchacha,
|
||||
LongNameMax: args.longnamemax,
|
||||
})
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.WriteConf)
|
||||
|
|
|
@ -55,6 +55,8 @@ type ConfFile struct {
|
|||
FeatureFlags []string
|
||||
// FIDO2 parameters
|
||||
FIDO2 *FIDO2Params `json:",omitempty"`
|
||||
// LongNameMax corresponds to the -longnamemax flag
|
||||
LongNameMax uint8 `json:",omitempty"`
|
||||
// Filename is the name of the config file. Not exported to JSON.
|
||||
filename string
|
||||
}
|
||||
|
@ -71,6 +73,7 @@ type CreateArgs struct {
|
|||
Fido2HmacSalt []byte
|
||||
DeterministicNames bool
|
||||
XChaCha20Poly1305 bool
|
||||
LongNameMax uint8
|
||||
}
|
||||
|
||||
// Create - create a new config with a random key encrypted with
|
||||
|
@ -97,6 +100,12 @@ func Create(args *CreateArgs) error {
|
|||
if !args.DeterministicNames {
|
||||
cf.setFeatureFlag(FlagDirIV)
|
||||
}
|
||||
// 0 means to *use* the default (which means we don't have to save it), and
|
||||
// 255 *is* the default, which means we don't have to save it either.
|
||||
if args.LongNameMax != 0 && args.LongNameMax != 255 {
|
||||
cf.LongNameMax = args.LongNameMax
|
||||
cf.setFeatureFlag(FlagLongNameMax)
|
||||
}
|
||||
cf.setFeatureFlag(FlagEMENames)
|
||||
cf.setFeatureFlag(FlagLongNames)
|
||||
cf.setFeatureFlag(FlagRaw64)
|
||||
|
|
|
@ -131,6 +131,30 @@ func TestCreateConfFileAESSIV(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCreateConfLongNameMax(t *testing.T) {
|
||||
args := &CreateArgs{
|
||||
Filename: "config_test/tmp.conf",
|
||||
Password: testPw,
|
||||
LogN: 10,
|
||||
Creator: "test",
|
||||
LongNameMax: 100,
|
||||
}
|
||||
err := Create(args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, c, err := LoadAndDecrypt("config_test/tmp.conf", testPw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !c.IsFeatureFlagSet(FlagLongNameMax) {
|
||||
t.Error("FlagLongNameMax should be set but is not")
|
||||
}
|
||||
if c.LongNameMax != args.LongNameMax {
|
||||
t.Errorf("wrong LongNameMax value: want=%d have=%d", args.LongNameMax, c.LongNameMax)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFeatureFlagKnown(t *testing.T) {
|
||||
// Test a few hardcoded values
|
||||
testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames", "AESSIV"}
|
||||
|
|
|
@ -14,8 +14,11 @@ const (
|
|||
// This flag is mandatory since gocryptfs v1.0,
|
||||
// except when XChaCha20Poly1305 is used.
|
||||
FlagGCMIV128
|
||||
// FlagLongNames allows file names longer than 176 bytes.
|
||||
// FlagLongNames allows file names longer than 175 bytes.
|
||||
FlagLongNames
|
||||
// FlagLongNameMax sets a custom name length limit, names longer than that
|
||||
// will be hashed.
|
||||
FlagLongNameMax
|
||||
// FlagAESSIV selects an AES-SIV based crypto backend.
|
||||
FlagAESSIV
|
||||
// FlagRaw64 enables raw (unpadded) base64 encoding for file names
|
||||
|
@ -40,6 +43,7 @@ var knownFlags = map[flagIota]string{
|
|||
FlagEMENames: "EMENames",
|
||||
FlagGCMIV128: "GCMIV128",
|
||||
FlagLongNames: "LongNames",
|
||||
FlagLongNameMax: "LongNameMax",
|
||||
FlagAESSIV: "AESSIV",
|
||||
FlagRaw64: "Raw64",
|
||||
FlagHKDF: "HKDF",
|
||||
|
|
|
@ -23,22 +23,23 @@ func (cf *ConfFile) Validate() error {
|
|||
}
|
||||
// File content encryption
|
||||
{
|
||||
switch {
|
||||
case cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && cf.IsFeatureFlagSet(FlagAESSIV):
|
||||
if cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && cf.IsFeatureFlagSet(FlagAESSIV) {
|
||||
return fmt.Errorf("Can't have both XChaCha20Poly1305 and AESSIV feature flags")
|
||||
case cf.IsFeatureFlagSet(FlagAESSIV):
|
||||
if !cf.IsFeatureFlagSet(FlagGCMIV128) {
|
||||
return fmt.Errorf("AESSIV requires GCMIV128 feature flag")
|
||||
}
|
||||
case cf.IsFeatureFlagSet(FlagXChaCha20Poly1305):
|
||||
}
|
||||
if cf.IsFeatureFlagSet(FlagAESSIV) && !cf.IsFeatureFlagSet(FlagGCMIV128) {
|
||||
|
||||
return fmt.Errorf("AESSIV requires GCMIV128 feature flag")
|
||||
}
|
||||
if cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) {
|
||||
if cf.IsFeatureFlagSet(FlagGCMIV128) {
|
||||
return fmt.Errorf("XChaCha20Poly1305 conflicts with GCMIV128 feature flag")
|
||||
}
|
||||
if !cf.IsFeatureFlagSet(FlagHKDF) {
|
||||
return fmt.Errorf("XChaCha20Poly1305 requires HKDF feature flag")
|
||||
}
|
||||
}
|
||||
// The absence of other flags means AES-GCM (oldest algorithm)
|
||||
case !cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && !cf.IsFeatureFlagSet(FlagAESSIV):
|
||||
if !cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && !cf.IsFeatureFlagSet(FlagAESSIV) {
|
||||
if !cf.IsFeatureFlagSet(FlagGCMIV128) {
|
||||
return fmt.Errorf("AES-GCM requires GCMIV128 feature flag")
|
||||
}
|
||||
|
@ -46,10 +47,10 @@ func (cf *ConfFile) Validate() error {
|
|||
}
|
||||
// Filename encryption
|
||||
{
|
||||
switch {
|
||||
case cf.IsFeatureFlagSet(FlagPlaintextNames) && cf.IsFeatureFlagSet(FlagEMENames):
|
||||
return fmt.Errorf("Can't have both PlaintextNames and EMENames feature flags")
|
||||
case cf.IsFeatureFlagSet(FlagPlaintextNames):
|
||||
if cf.IsFeatureFlagSet(FlagPlaintextNames) {
|
||||
if cf.IsFeatureFlagSet(FlagEMENames) {
|
||||
return fmt.Errorf("PlaintextNames conflicts with EMENames feature flag")
|
||||
}
|
||||
if cf.IsFeatureFlagSet(FlagDirIV) {
|
||||
return fmt.Errorf("PlaintextNames conflicts with DirIV feature flag")
|
||||
}
|
||||
|
@ -59,9 +60,19 @@ func (cf *ConfFile) Validate() error {
|
|||
if cf.IsFeatureFlagSet(FlagRaw64) {
|
||||
return fmt.Errorf("PlaintextNames conflicts with Raw64 feature flag")
|
||||
}
|
||||
case cf.IsFeatureFlagSet(FlagEMENames):
|
||||
if cf.IsFeatureFlagSet(FlagLongNameMax) {
|
||||
return fmt.Errorf("PlaintextNames conflicts with LongNameMax feature flag")
|
||||
}
|
||||
}
|
||||
if cf.IsFeatureFlagSet(FlagEMENames) {
|
||||
// All combinations of DirIV, LongNames, Raw64 allowed
|
||||
}
|
||||
if cf.LongNameMax != 0 && !cf.IsFeatureFlagSet(FlagLongNameMax) {
|
||||
return fmt.Errorf("LongNameMax=%d but the LongNameMax feature flag is NOT set", cf.LongNameMax)
|
||||
}
|
||||
if cf.LongNameMax == 0 && cf.IsFeatureFlagSet(FlagLongNameMax) {
|
||||
return fmt.Errorf("LongNameMax=0 but the LongNameMax feature flag IS set")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -41,6 +41,10 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
|
|||
n.translateSize(dirfd, cName, &out.Attr)
|
||||
|
||||
rn := n.rootNode()
|
||||
if rn.args.ForceOwner != nil {
|
||||
out.Owner = *rn.args.ForceOwner
|
||||
}
|
||||
|
||||
if rn.args.SharedStorage {
|
||||
// If we already have a child node that matches what we found on disk*
|
||||
// (as reflected in `ch`), return it here.
|
||||
|
|
|
@ -103,6 +103,12 @@ func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint3
|
|||
if errno != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
inode = n.newChild(ctx, st, out)
|
||||
|
||||
if rn.args.ForceOwner != nil {
|
||||
out.Owner = *rn.args.ForceOwner
|
||||
}
|
||||
|
||||
return inode, fh, fuseFlags, errno
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
|
|||
defer syscall.Close(dirfd)
|
||||
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return nil, fs.ToErrno(err)
|
||||
}
|
||||
|
@ -49,10 +49,10 @@ func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags
|
|||
defer syscall.Close(dirfd)
|
||||
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
|
||||
// Directories cannot be opened read-write. Retry.
|
||||
if err == syscall.EISDIR {
|
||||
fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK, 0)
|
||||
fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
|
||||
}
|
||||
if err != nil {
|
||||
fs.ToErrno(err)
|
||||
|
@ -71,10 +71,10 @@ func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
|
|||
defer syscall.Close(dirfd)
|
||||
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
|
||||
// Directories cannot be opened read-write. Retry.
|
||||
if err == syscall.EISDIR {
|
||||
fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK, 0)
|
||||
fd, err = syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return fs.ToErrno(err)
|
||||
|
@ -93,7 +93,7 @@ func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
|
|||
defer syscall.Close(dirfd)
|
||||
|
||||
// O_NONBLOCK to not block on FIFOs.
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NONBLOCK|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return nil, fs.ToErrno(err)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func newTestFS(args Args) *RootNode {
|
|||
key := make([]byte, cryptocore.KeyLen)
|
||||
cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true)
|
||||
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
|
||||
n := nametransform.New(cCore.EMECipher, true, true, nil, false)
|
||||
n := nametransform.New(cCore.EMECipher, true, 0, true, nil, false)
|
||||
rn := NewRootNode(args, cEnc, n)
|
||||
oneSec := time.Second
|
||||
options := &fs.Options{
|
||||
|
|
|
@ -48,7 +48,7 @@ func (be *NameTransform) EncryptAndHashBadName(name string, iv []byte, dirfd int
|
|||
//expand suffix on error
|
||||
continue
|
||||
}
|
||||
if be.longNames && len(cName) > NameMax {
|
||||
if len(cName) > be.longNameMax {
|
||||
cNamePart = be.HashLongName(cName)
|
||||
}
|
||||
cNameBadReverse := cNamePart + name[charpos:len(name)-len(BadnameSuffix)]
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package nametransform
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
)
|
||||
|
||||
func TestIsLongName(t *testing.T) {
|
||||
|
@ -28,3 +32,40 @@ func TestRemoveLongNameSuffix(t *testing.T) {
|
|||
t.Error(".name suffix not removed")
|
||||
}
|
||||
}
|
||||
|
||||
func newLognamesTestInstance(longNameMax uint8) *NameTransform {
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true)
|
||||
return New(cCore.EMECipher, true, longNameMax, true, nil, false)
|
||||
}
|
||||
|
||||
func TestLongNameMax(t *testing.T) {
|
||||
iv := make([]byte, 16)
|
||||
for max := 0; max <= NameMax; max++ {
|
||||
n := newLognamesTestInstance(uint8(max))
|
||||
if max == 0 {
|
||||
// effective value is 255
|
||||
max = NameMax
|
||||
}
|
||||
for l := 0; l <= NameMax+10; l++ {
|
||||
name := strings.Repeat("x", l)
|
||||
out, err := n.EncryptAndHashName(name, iv)
|
||||
if l == 0 || l > NameMax {
|
||||
if err == nil {
|
||||
t.Errorf("should have rejected a name of length %d, but did not", l)
|
||||
}
|
||||
continue
|
||||
}
|
||||
cName, _ := n.EncryptName(name, iv)
|
||||
rawLen := len(cName)
|
||||
want := LongNameNone
|
||||
if rawLen > max {
|
||||
want = LongNameContent
|
||||
}
|
||||
have := NameType(out)
|
||||
if have != want {
|
||||
t.Errorf("l=%d max=%d: wanted %v, got %v\nname=%q\nout=%q", l, max, want, have, name, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ package nametransform
|
|||
import (
|
||||
"crypto/aes"
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
|
@ -20,7 +21,9 @@ const (
|
|||
// NameTransform is used to transform filenames.
|
||||
type NameTransform struct {
|
||||
emeCipher *eme.EMECipher
|
||||
longNames bool
|
||||
// Names longer than `longNameMax` are hashed. Set to MaxInt when
|
||||
// longnames are disabled.
|
||||
longNameMax int
|
||||
// B64 = either base64.URLEncoding or base64.RawURLEncoding, depending
|
||||
// on the Raw64 feature flag
|
||||
B64 *base64.Encoding
|
||||
|
@ -30,17 +33,28 @@ type NameTransform struct {
|
|||
}
|
||||
|
||||
// New returns a new NameTransform instance.
|
||||
func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string, deterministicNames bool) *NameTransform {
|
||||
tlog.Debug.Printf("nametransform.New: longNames=%v, raw64=%v, badname=%q",
|
||||
longNames, raw64, badname)
|
||||
|
||||
//
|
||||
// If `longNames` is set, names longer than `longNameMax` are hashed to
|
||||
// `gocryptfs.longname.[sha256]`.
|
||||
// Pass `longNameMax = 0` to use the default value (255).
|
||||
func New(e *eme.EMECipher, longNames bool, longNameMax uint8, raw64 bool, badname []string, deterministicNames bool) *NameTransform {
|
||||
tlog.Debug.Printf("nametransform.New: longNameMax=%v, raw64=%v, badname=%q",
|
||||
longNameMax, raw64, badname)
|
||||
b64 := base64.URLEncoding
|
||||
if raw64 {
|
||||
b64 = base64.RawURLEncoding
|
||||
}
|
||||
var effectiveLongNameMax int = math.MaxInt32
|
||||
if longNames {
|
||||
if longNameMax == 0 {
|
||||
effectiveLongNameMax = NameMax
|
||||
} else {
|
||||
effectiveLongNameMax = int(longNameMax)
|
||||
}
|
||||
}
|
||||
return &NameTransform{
|
||||
emeCipher: e,
|
||||
longNames: longNames,
|
||||
longNameMax: effectiveLongNameMax,
|
||||
B64: b64,
|
||||
badnamePatterns: badname,
|
||||
deterministicNames: deterministicNames,
|
||||
|
@ -115,7 +129,7 @@ func (be *NameTransform) EncryptAndHashName(name string, iv []byte) (string, err
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if be.longNames && len(cName) > NameMax {
|
||||
if len(cName) > be.longNameMax {
|
||||
return be.HashLongName(cName), nil
|
||||
}
|
||||
return cName, nil
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// O_DIRECT means oncached I/O on Linux. No direct equivalent on MacOS and defined
|
||||
// O_DIRECT means uncached I/O on Linux. No direct equivalent on MacOS and defined
|
||||
// to zero there.
|
||||
O_DIRECT = 0
|
||||
|
||||
|
|
|
@ -106,7 +106,11 @@ var Warn *toggledLogger
|
|||
var Fatal *toggledLogger
|
||||
|
||||
func init() {
|
||||
if term.IsTerminal(int(os.Stdout.Fd())) {
|
||||
// Enable color output if we are connected to a terminal and NO_COLOR is
|
||||
// unset ( https://no-color.org/ ).
|
||||
if _, nocolor := os.LookupEnv("NO_COLOR"); !nocolor &&
|
||||
term.IsTerminal(int(os.Stdout.Fd())) &&
|
||||
term.IsTerminal(int(os.Stderr.Fd())) {
|
||||
ColorReset = "\033[0m"
|
||||
ColorGrey = "\033[2m"
|
||||
ColorRed = "\033[31m"
|
||||
|
|
4
mount.go
4
mount.go
|
@ -292,6 +292,8 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
|
|||
// Settings from the config file override command line args
|
||||
frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames)
|
||||
frontendArgs.DeterministicNames = !confFile.IsFeatureFlagSet(configfile.FlagDirIV)
|
||||
// Things that don't have to be in frontendArgs are only in args
|
||||
args.longnamemax = confFile.LongNameMax
|
||||
args.raw64 = confFile.IsFeatureFlagSet(configfile.FlagRaw64)
|
||||
args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF)
|
||||
// Note: this will always return the non-openssl variant
|
||||
|
@ -324,7 +326,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
|
|||
// Init crypto backend
|
||||
cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf)
|
||||
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
|
||||
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames,
|
||||
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, args.longnamemax,
|
||||
args.raw64, []string(args.badname), frontendArgs.DeterministicNames)
|
||||
// After the crypto backend is initialized,
|
||||
// we can purge the master key from memory.
|
||||
|
|
|
@ -93,4 +93,12 @@ if find . -type f -name \*.go -print0 | xargs -0 grep -E 'syscall.(Setegid|Seteu
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if find . -type f -name \*.go -print0 | xargs -0 grep '\.Creat('; then
|
||||
# MacOS does not have syscall.Creat(). Creat() is equivalent to Open(..., O_CREAT|O_WRONLY|O_TRUNC, ...),
|
||||
# but usually you want O_EXCL instead of O_TRUNC because it is safer, so that's what we suggest
|
||||
# instead.
|
||||
echo "$MYNAME: Please use Open(..., O_CREAT|O_WRONLY|O_EXCL, ...) instead of Creat()! https://github.com/rfjakob/gocryptfs/issues/623"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
) 200> "$LOCKFILE"
|
||||
|
|
|
@ -982,7 +982,7 @@ func TestMountCreat(t *testing.T) {
|
|||
for i := 0; i < concurrency; i++ {
|
||||
go func(i int) {
|
||||
path := fmt.Sprintf("%s/%d", mnt, i)
|
||||
fd, err := syscall.Creat(path, 0600)
|
||||
fd, err := syscall.Open(path, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 0600)
|
||||
syscall.Close(fd)
|
||||
if err != nil {
|
||||
t.Errorf("Creat %q: %v", path, err)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
|
||||
)
|
||||
|
||||
// Create & test fs with -longnamemax=100
|
||||
func TestLongnamemax100(t *testing.T) {
|
||||
cDir := test_helpers.InitFS(nil, "-longnamemax", "100")
|
||||
pDir := cDir + ".mnt"
|
||||
|
||||
// Check config file sanity
|
||||
_, c, err := configfile.LoadAndDecrypt(cDir+"/"+configfile.ConfDefaultName, testPw)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !c.IsFeatureFlagSet(configfile.FlagLongNameMax) {
|
||||
t.Error("FlagLongNameMax should be on")
|
||||
}
|
||||
if c.LongNameMax != 100 {
|
||||
t.Errorf("LongNameMax=%d, want 100", c.LongNameMax)
|
||||
}
|
||||
|
||||
// Check that it takes effect
|
||||
test_helpers.MountOrExit(cDir, pDir, "-extpass", "echo test")
|
||||
defer test_helpers.UnmountPanic(pDir)
|
||||
|
||||
for l := 1; l <= 255; l++ {
|
||||
path := pDir + "/" + strings.Repeat("x", l)
|
||||
if err := ioutil.WriteFile(path, nil, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
matches, err := filepath.Glob(cDir + "/gocryptfs.longname.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = syscall.Unlink(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// As determined experimentally, a name of length >= 64 causes a longname
|
||||
// to be created.
|
||||
if l <= 63 && len(matches) != 0 {
|
||||
t.Errorf("l=%d: should not see a longname yet", l)
|
||||
}
|
||||
if l >= 64 && len(matches) != 2 {
|
||||
t.Errorf("l=%d: should see a longname now", l)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -401,7 +401,7 @@ func TestMaxlen(t *testing.T) {
|
|||
|
||||
func TestFsync(t *testing.T) {
|
||||
fileName := test_helpers.DefaultPlainDir + "/" + t.Name() + ".file"
|
||||
fileFD, err := syscall.Creat(fileName, 0600)
|
||||
fileFD, err := syscall.Open(fileName, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -425,3 +425,69 @@ func TestFsync(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// force_owner was broken by the v2.0 rewrite:
|
||||
// The owner was only forced for GETATTR, but not for CREATE or LOOKUP.
|
||||
//
|
||||
// https://github.com/rfjakob/gocryptfs/issues/609
|
||||
// https://github.com/rfjakob/gocryptfs/pull/610
|
||||
func TestForceOwner(t *testing.T) {
|
||||
cDir := test_helpers.InitFS(t)
|
||||
os.Chmod(cDir, 0777) // Mount needs to be accessible for us
|
||||
pDir := cDir + ".mnt"
|
||||
test_helpers.MountOrFatal(t, cDir, pDir, "-force_owner=1234:1234", "-extpass=echo test")
|
||||
defer test_helpers.UnmountPanic(pDir)
|
||||
|
||||
// We need an unrestricted umask
|
||||
oldmask := syscall.Umask(0)
|
||||
defer syscall.Umask(oldmask)
|
||||
|
||||
foo := pDir + "/foo"
|
||||
|
||||
// In the answer to a FUSE CREATE, gocryptfs sends file information including
|
||||
// the owner. This is cached by the kernel and will be used for the next
|
||||
// stat() call.
|
||||
fd, err := syscall.Open(foo, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
syscall.Close(fd)
|
||||
|
||||
var st syscall.Stat_t
|
||||
if err := syscall.Stat(foo, &st); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Uid != 1234 || st.Gid != 1234 {
|
||||
t.Errorf("CREATE returned uid or gid != 1234: %#v", st)
|
||||
}
|
||||
|
||||
// We can clear the kernel stat() cache by writing to the file
|
||||
fd, err = syscall.Open(foo, syscall.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := syscall.Write(fd, []byte("hello world")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
syscall.Close(fd)
|
||||
|
||||
// This stat() triggers a new GETATTR
|
||||
if err := syscall.Stat(foo, &st); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Uid != 1234 || st.Gid != 1234 {
|
||||
t.Errorf("GETATTR returned uid or gid != 1234: %#v", st)
|
||||
}
|
||||
|
||||
// Remount to clear cache
|
||||
test_helpers.UnmountPanic(pDir)
|
||||
test_helpers.MountOrFatal(t, cDir, pDir, "-force_owner=1234:1234", "-extpass=echo test")
|
||||
|
||||
// This stat() triggers a new LOOKUP
|
||||
if err := syscall.Stat(foo, &st); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Uid != 1234 || st.Gid != 1234 {
|
||||
t.Errorf("LOOKUP returned uid or gid != 1234: %#v", st)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ func TestInoReuse(t *testing.T) {
|
|||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
fd, err := syscall.Creat(fn, 0600)
|
||||
fd, err := syscall.Open(fn, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 0600)
|
||||
if err == syscall.EISDIR {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ func TestInoReuseEvil(t *testing.T) {
|
|||
}
|
||||
// create a new file that will likely get the same inode number
|
||||
pPath2 := pPath + "2"
|
||||
fd, err := syscall.Creat(pPath2, 0600)
|
||||
fd, err := syscall.Open(pPath2, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//+build linux
|
||||
|
||||
// Package root_test contains tests that need root
|
||||
// permissions to run
|
||||
package root_test
|
||||
|
@ -120,7 +122,7 @@ func writeTillFull(t *testing.T, path string) (int, syscall.Errno) {
|
|||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
fd, err := syscall.Creat(path, 0600)
|
||||
fd, err := syscall.Open(path, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return 0, err.(syscall.Errno)
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ func TestDirUnlink(t *testing.T) {
|
|||
if err := unix.Rmdir(tc.mnt2 + "/foo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fd, err := unix.Creat(tc.mnt2+"/foo", 0600); err != nil {
|
||||
if fd, err := unix.Open(tc.mnt2+"/foo", unix.O_CREAT|unix.O_WRONLY|unix.O_TRUNC, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
unix.Close(fd)
|
||||
|
@ -104,7 +104,7 @@ func TestStaleHardlinks(t *testing.T) {
|
|||
defer tc.cleanup()
|
||||
|
||||
link0 := tc.mnt1 + "/link0"
|
||||
if fd, err := unix.Creat(link0, 0600); err != nil {
|
||||
if fd, err := unix.Open(link0, unix.O_CREAT|unix.O_WRONLY|unix.O_TRUNC, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
unix.Close(fd)
|
||||
|
|
Loading…
Reference in New Issue