Genesis patch
This commit is contained in:
parent
9046d6d922
commit
847d4fa781
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,22 +1,10 @@
|
||||
# the gocryptfs executable
|
||||
/gocryptfs
|
||||
/build
|
||||
/include
|
||||
/lib
|
||||
/openssl*
|
||||
|
||||
# temporary files created by the tests
|
||||
/tmp
|
||||
|
||||
# binary releases and signatiures
|
||||
/*.tar.gz
|
||||
/*.asc
|
||||
|
||||
# Binaries created for cpu profiling
|
||||
*.test
|
||||
|
||||
# Rendered manpage
|
||||
gocryptfs.1
|
||||
|
||||
# Dependencies copied by "dep"
|
||||
/vendor
|
||||
/_vendor-*
|
||||
|
||||
# Source tarball version. Should never be committed to git.
|
||||
/VERSION
|
||||
|
43
.travis.yml
43
.travis.yml
@ -1,43 +0,0 @@
|
||||
language: go
|
||||
os: linux
|
||||
|
||||
# fuse on travis
|
||||
sudo: required
|
||||
dist: bionic # Ubuntu 18.04 "Bionic", https://docs.travis-ci.com/user/reference/bionic/
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
git:
|
||||
depth: 300
|
||||
|
||||
# Build with the lastest relevant Go versions
|
||||
# Relevance is determined from:
|
||||
# * https://golang.org/dl/
|
||||
# * https://packages.debian.org/search?keywords=golang&searchon=names&exact=1&suite=all§ion=all
|
||||
# * https://packages.ubuntu.com/search?keywords=golang&searchon=names&exact=1&suite=all§ion=all
|
||||
go:
|
||||
- 1.11.x # Debian 10 "Buster"
|
||||
- 1.12.x # Ubuntu 19.10
|
||||
- 1.13.x # Debian 11 "Bullseye"
|
||||
- stable
|
||||
|
||||
before_install:
|
||||
- sudo apt-get install -qq fuse
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
|
||||
script:
|
||||
- openssl version
|
||||
- df -Th / /tmp
|
||||
- env GO111MODULE=on go build
|
||||
- ./build-without-openssl.bash
|
||||
- ./build.bash
|
||||
- ./gocryptfs -speed
|
||||
- ./test.bash
|
||||
- make root_test
|
||||
- ./crossbuild.bash
|
||||
- echo "rebuild with locked dependencies"
|
||||
- go mod vendor
|
||||
- ./build.bash -mod=vendor
|
2
Documentation/.gitignore
vendored
2
Documentation/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
# Generated man pages
|
||||
*.1
|
@ -1,102 +0,0 @@
|
||||
Stable CLI ABI
|
||||
==============
|
||||
|
||||
If you want to call gocryptfs from your script or app, this is the
|
||||
stable ABI.
|
||||
|
||||
General
|
||||
-------
|
||||
|
||||
1. A password is piped into gocryptfs with an optional terminating
|
||||
newline. Any unexpected data after the final newline will
|
||||
cause gocryptfs to abort.
|
||||
2. Always pass "--" after the options. This prevents a CIPERDIR that
|
||||
starts with a dash ("-") to wreak havoc.
|
||||
3. Use "-q" to get rid of all informational messages. Only error
|
||||
messages (if any) will be printed to stderr (capture it!).
|
||||
4. Check the exit code of gocryptfs. 0 is success, anything else is an
|
||||
error and details about that error will have been printed to stderr.
|
||||
|
||||
Initialize Filesystem
|
||||
---------------------
|
||||
|
||||
#### Bash example
|
||||
|
||||
$ cat mypassword.txt | gocryptfs -init -q -- CIPHERDIR
|
||||
|
||||
Content of "mypassword.txt":
|
||||
|
||||
mypassword1234
|
||||
|
||||
#### What you have to pipe to gocryptfs
|
||||
|
||||
1. Password
|
||||
2. Optional newline
|
||||
|
||||
#### Notes
|
||||
|
||||
1. The CIPHERDIR directory must exist and be empty
|
||||
|
||||
#### Exit Codes
|
||||
|
||||
* 0 = success
|
||||
* 6 = CIPHERDIR is invalid: not an empty directory
|
||||
* 22 = password is empty
|
||||
* 24 = could not create gocryptfs.conf
|
||||
* other = please inspect the message
|
||||
|
||||
Mount
|
||||
-----
|
||||
|
||||
#### Bash example
|
||||
|
||||
$ cat mypassword.txt | gocryptfs -q -- CIPHERDIR MOUNTPOINT
|
||||
|
||||
#### What you have to pipe to gocryptfs
|
||||
|
||||
Same as for "Initialize Filesystem".
|
||||
|
||||
#### Notes
|
||||
|
||||
1. The MOUNTPOINT directory must exist and be empty.
|
||||
|
||||
#### Exit Codes
|
||||
|
||||
* 0 = success
|
||||
* 10 = MOUNTPOINT is not an empty directory or contains CIPHERDIR
|
||||
* 12 = password incorrect
|
||||
* 23 = gocryptfs.conf could not be opened (does not exist, is unreadable, ...)
|
||||
* other = please inspect the message
|
||||
|
||||
Change Password
|
||||
---------------
|
||||
|
||||
#### Bash example
|
||||
|
||||
$ cat change.txt | gocryptfs -passwd -q -- CIPHERDIR
|
||||
|
||||
Content of "change.txt":
|
||||
|
||||
mypassword1234
|
||||
newpassword9876
|
||||
|
||||
#### What you have to pipe to gocryptfs
|
||||
|
||||
1. Old password
|
||||
2. Newline
|
||||
3. New password
|
||||
4. Optional newline
|
||||
|
||||
#### Exit Codes
|
||||
|
||||
* 0 = success
|
||||
* 12 = password incorrect
|
||||
* 23 = gocryptfs.conf could not be opened for reading
|
||||
* 24 = could not write the updated gocryptfs.conf
|
||||
* other = please inspect the message
|
||||
|
||||
Further Reading
|
||||
---------------
|
||||
|
||||
Additional exit codes that are unlikely to occur are defined in
|
||||
[exitcodes.go](../internal/exitcodes/exitcodes.go).
|
@ -1,82 +0,0 @@
|
||||
% STATFS(1)
|
||||
% github.com/rfjakob
|
||||
% Sep 2019
|
||||
|
||||
NAME
|
||||
====
|
||||
|
||||
statfs - dump the statfs(2) information for PATH to console in JSON format.
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
statfs PATH
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
The statfs(2) system call returns information about a mounted filesystem
|
||||
in a `statfs_t` structure. This tool dumps this information in JSON format.
|
||||
It is developed as part of gocryptfs and written in Go.
|
||||
|
||||
The `statfs_t` structure is architecture-dependent. On amd64 it looks like this:
|
||||
|
||||
```
|
||||
type Statfs_t struct {
|
||||
Type int64
|
||||
Bsize int64
|
||||
Blocks uint64
|
||||
Bfree uint64
|
||||
Bavail uint64
|
||||
Files uint64
|
||||
Ffree uint64
|
||||
Fsid struct {
|
||||
Val [2]int32
|
||||
}
|
||||
Namelen int64
|
||||
Frsize int64
|
||||
Flags int64
|
||||
Spare [4]int64
|
||||
}
|
||||
```
|
||||
|
||||
See the statfs(2) man page for the meaning of these fields, and note
|
||||
that the field names here are acc. to the Go `golang.org/x/sys/unix`
|
||||
naming convention, and slightly different than in C.
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
|
||||
Get the statfs(2) information for /tmp:
|
||||
|
||||
```
|
||||
$ statfs /tmp
|
||||
{
|
||||
"Type": 16914836,
|
||||
"Bsize": 4096,
|
||||
"Blocks": 3067428,
|
||||
"Bfree": 3067411,
|
||||
"Bavail": 3067411,
|
||||
"Files": 3067428,
|
||||
"Ffree": 3067381,
|
||||
"Fsid": {
|
||||
"Val": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"Namelen": 255,
|
||||
"Frsize": 4096,
|
||||
"Flags": 38,
|
||||
"Spare": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
statfs(2) gocryptfs(1)
|
@ -1,64 +0,0 @@
|
||||
% GOCRYPTFS-XRAY(1)
|
||||
% github.com/rfjakob
|
||||
% Jan 2018
|
||||
|
||||
NAME
|
||||
====
|
||||
|
||||
gocryptfs-xray - examine gocryptfs-related data
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
#### Examine encrypted file/directory
|
||||
gocryptfs-xray CIPHERDIR/ENCRYPTED-FILE-OR-DIR
|
||||
|
||||
#### Decrypt and show master key
|
||||
gocryptfs-xray -dumpmasterkey CIPHERDIR/gocryptfs.conf
|
||||
|
||||
#### Encrypt paths
|
||||
gocryptfs-xray -encrypt-paths SOCKET
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
Available options are listed below.
|
||||
|
||||
#### -0
|
||||
Use \\0 instead of \\n as separator for -decrypt-paths and -encrypt-paths.
|
||||
|
||||
#### -aessiv
|
||||
Assume AES-SIV mode instead of AES-GCM when examining an encrypted file.
|
||||
Is not needed and has no effect in `-dumpmasterkey` mode.
|
||||
|
||||
#### -decrypt-paths
|
||||
Decrypt file paths using gocryptfs control socket. Reads from stdin.
|
||||
See `-ctlsock` in gocryptfs(1).
|
||||
|
||||
#### -dumpmasterkey
|
||||
Decrypts and shows the master key.
|
||||
|
||||
#### -encrypt-paths
|
||||
Encrypt file paths using gocryptfs control socket. Reads from stdin.
|
||||
See `-ctlsock` in gocryptfs(1).
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
|
||||
Examine an encrypted file:
|
||||
|
||||
gocryptfs-xray myfs/mCXnISiv7nEmyc0glGuhTQ
|
||||
|
||||
Print the master key:
|
||||
|
||||
gocryptfs-xray -dumpmasterkey myfs/gocryptfs.conf
|
||||
|
||||
Mount gocryptfs with control socket and use gocryptfs-xray to
|
||||
encrypt some paths:
|
||||
|
||||
gocryptfs -ctlsock myfs.sock myfs myfs.mnt
|
||||
echo -e "foo\nbar" | gocryptfs-xray -encrypt-paths myfs.sock
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
gocryptfs(1) fuse(8)
|
@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
cd $(dirname "$0")
|
||||
|
||||
# Render Markdown to a proper man(1) manpage
|
||||
function render {
|
||||
IN=$1
|
||||
OUT=$2
|
||||
echo "Rendering $IN to $OUT"
|
||||
echo ".\\\" This man page was generated from $IN. View it using 'man ./$OUT'" > $OUT
|
||||
echo ".\\\"" >> $OUT
|
||||
pandoc "$IN" -s -t man >> $OUT
|
||||
}
|
||||
|
||||
render MANPAGE.md gocryptfs.1
|
||||
render MANPAGE-XRAY.md gocryptfs-xray.1
|
||||
render MANPAGE-STATFS.md statfs.1
|
@ -1,619 +0,0 @@
|
||||
% GOCRYPTFS(1)
|
||||
% github.com/rfjakob
|
||||
% Aug 2017
|
||||
|
||||
NAME
|
||||
====
|
||||
|
||||
gocryptfs - create or mount an encrypted filesystem
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
#### Initialize new encrypted filesystem
|
||||
`gocryptfs -init [OPTIONS] CIPHERDIR`
|
||||
|
||||
#### Mount
|
||||
`gocryptfs [OPTIONS] CIPHERDIR MOUNTPOINT [-o COMMA-SEPARATED-OPTIONS]`
|
||||
|
||||
#### Unmount
|
||||
`fusermount -u MOUNTPOINT`
|
||||
|
||||
#### Change password
|
||||
`gocryptfs -passwd [OPTIONS] CIPHERDIR`
|
||||
|
||||
#### Check consistency
|
||||
`gocryptfs -fsck [OPTIONS] CIPHERDIR`
|
||||
|
||||
#### Show filesystem information
|
||||
`gocryptfs -info [OPTIONS] CIPHERDIR`
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
gocryptfs is an encrypted overlay filesystem written in Go.
|
||||
Encrypted files are stored in CIPHERDIR, and a plain-text
|
||||
view can be presented by mounting the filesystem at MOUNTPOINT.
|
||||
|
||||
gocryptfs was inspired by encfs(1) and strives to fix its
|
||||
security issues while providing good performance.
|
||||
|
||||
ACTION FLAGS
|
||||
============
|
||||
|
||||
Unless one of the following *action flags* is passed, the default
|
||||
action is to mount a filesystem (see SYNOPSIS).
|
||||
|
||||
#### -fsck
|
||||
Check CIPHERDIR for consistency. If corruption is found, the
|
||||
exit code is 26.
|
||||
|
||||
#### -h, -help
|
||||
Print a short help text that shows the more-often used options.
|
||||
|
||||
#### -hh
|
||||
Long help text, shows all available options.
|
||||
|
||||
#### -info
|
||||
Pretty-print the contents of the config file in CIPHERDIR for
|
||||
human consumption, stripping out sensitive data.
|
||||
|
||||
Example:
|
||||
|
||||
$ gocryptfs -info my_cipherdir
|
||||
Creator: gocryptfs v2.0-beta2
|
||||
FeatureFlags: GCMIV128 HKDF DirIV EMENames LongNames Raw64
|
||||
EncryptedKey: 64B
|
||||
ScryptObject: Salt=32B N=65536 R=8 P=1 KeyLen=32
|
||||
|
||||
#### -init
|
||||
Initialize encrypted directory.
|
||||
|
||||
#### -passwd
|
||||
Change the password. Will ask for the old password, check if it is
|
||||
correct, and ask for a new one.
|
||||
|
||||
This can be used together with `-masterkey` if
|
||||
you forgot the password but know the master key. Note that without the
|
||||
old password, gocryptfs cannot tell if the master key is correct and will
|
||||
overwrite the old one without mercy. It will, however, create a backup copy
|
||||
of the old config file as `gocryptfs.conf.bak`. Delete it after
|
||||
you have verified that you can access your files with the
|
||||
new password.
|
||||
|
||||
#### -speed
|
||||
Run crypto speed test. Benchmark Go's built-in GCM against OpenSSL
|
||||
(if available). The library that will be selected on "-openssl=auto"
|
||||
(the default) is marked as such.
|
||||
|
||||
#### -version
|
||||
Print version and exit. The output contains three fields separated by ";".
|
||||
Example: "gocryptfs v1.1.1-5-g75b776c; go-fuse 6b801d3; 2016-11-01 go1.7.3".
|
||||
Field 1 is the gocryptfs version, field 2 is the version of the go-fuse
|
||||
library, field 3 is the compile date and the Go version that was
|
||||
used.
|
||||
|
||||
INIT OPTIONS
|
||||
============
|
||||
|
||||
Available options for `-init` are listed below. Usually, you don't need any.
|
||||
Defaults are fine.
|
||||
|
||||
#### -aessiv
|
||||
Use the AES-SIV encryption mode. This is slower than GCM but is
|
||||
secure with deterministic nonces as used in "-reverse" mode.
|
||||
|
||||
#### -devrandom
|
||||
Use `/dev/random` for generating the master key instead of the default Go
|
||||
implementation. This is especially useful on embedded systems with Go versions
|
||||
prior to 1.9, which fall back to weak random data when the getrandom syscall
|
||||
is blocking. Using this option can block indefinitely when the kernel cannot
|
||||
harvest enough entropy.
|
||||
|
||||
#### -hkdf
|
||||
Use HKDF to derive separate keys for content and name encryption from
|
||||
the master key. Default true.
|
||||
|
||||
#### -nosyslog
|
||||
Diagnostic messages are normally redirected to syslog once gocryptfs
|
||||
daemonizes. This option disables the redirection and messages will
|
||||
continue be printed to stdout and stderr.
|
||||
|
||||
#### -plaintextnames
|
||||
Do not encrypt file names and symlink targets.
|
||||
|
||||
#### -raw64
|
||||
Use unpadded base64 encoding for file names. This gets rid of the
|
||||
trailing "\\=\\=". A filesystem created with this option can only be
|
||||
mounted using gocryptfs v1.2 and higher. Default true.
|
||||
|
||||
#### -reverse
|
||||
Reverse mode shows a read-only encrypted view of a plaintext
|
||||
directory. Implies "-aessiv".
|
||||
|
||||
#### -scryptn int
|
||||
scrypt cost parameter expressed as scryptn=log2(N). Possible values are
|
||||
10 to 28, representing N=2^10 to N=2^28.
|
||||
|
||||
Setting this to a lower
|
||||
value speeds up mounting and reduces its memory needs, but makes
|
||||
the password susceptible to brute-force attacks. The default is 16.
|
||||
|
||||
MOUNT OPTIONS
|
||||
=============
|
||||
|
||||
Available options for mounting are listed below. Usually, you don't need any.
|
||||
Defaults are fine.
|
||||
|
||||
#### -allow_other
|
||||
By default, the Linux kernel prevents any other user (even root) to
|
||||
access a mounted FUSE filesystem. Settings this option allows access for
|
||||
other users, subject to file permission checking. Only works if
|
||||
user_allow_other is set in /etc/fuse.conf. This option is equivalent to
|
||||
"allow_other" plus "default_permissions" described in fuse(8).
|
||||
|
||||
#### -ctlsock string
|
||||
Create a control socket at the specified location. The socket can be
|
||||
used to decrypt and encrypt paths inside the filesystem. When using
|
||||
this option, make sure that the directory you place the socket in is
|
||||
not world-accessible. For example, `/run/user/UID/my.socket` would
|
||||
be suitable.
|
||||
|
||||
#### -dev, -nodev
|
||||
Enable (`-dev`) or disable (`-nodev`) device files in a gocryptfs mount
|
||||
(default: `-nodev`). If both are specified, `-nodev` takes precedence.
|
||||
You need root permissions to use `-dev`.
|
||||
|
||||
#### -e PATH, -exclude PATH
|
||||
Only for reverse mode: exclude relative plaintext path from the encrypted
|
||||
view, matching only from root of mounted filesystem. Can be passed multiple
|
||||
times. Example:
|
||||
|
||||
gocryptfs -reverse -exclude Music -exclude Movies /home/user /mnt/user.encrypted
|
||||
|
||||
See also `-exclude-wildcard`, `-exclude-from` and the [EXCLUDING FILES](#excluding-files) section.
|
||||
|
||||
#### -ew PATH, -exclude-wildcard PATH
|
||||
Only for reverse mode: exclude paths from the encrypted view, matching anywhere.
|
||||
Wildcards supported. Can be passed multiple times. Example:
|
||||
|
||||
gocryptfs -reverse -exclude-wildcard '*~' /home/user /mnt/user.encrypted
|
||||
|
||||
See also `-exclude`, `-exclude-from` and the [EXCLUDING FILES](#excluding-files) section.
|
||||
|
||||
#### -exclude-from FILE
|
||||
Only for reverse mode: reads exclusion patters (using `-exclude-wildcard` syntax)
|
||||
from a file. Can be passed multiple times. Example:
|
||||
|
||||
gocryptfs -reverse -exclude-from ~/crypt-exclusions /home/user /mnt/user.encrypted
|
||||
|
||||
See also `-exclude`, `-exclude-wildcard` and the [EXCLUDING FILES](#excluding-files) section.
|
||||
|
||||
#### -exec, -noexec
|
||||
Enable (`-exec`) or disable (`-noexec`) executables in a gocryptfs mount
|
||||
(default: `-exec`). If both are specified, `-noexec` takes precedence.
|
||||
|
||||
#### -fg, -f
|
||||
Stay in the foreground instead of forking away.
|
||||
For compatibility, "-f" is also accepted, but "-fg" is preferred.
|
||||
|
||||
Unless `-notifypid` is also passed, the logs go to stdout and stderr
|
||||
instead of syslog.
|
||||
|
||||
#### -force_owner string
|
||||
If given a string of the form "uid:gid" (where both "uid" and "gid" are
|
||||
substituted with positive integers), presents all files as owned by the given
|
||||
uid and gid, regardless of their actual ownership. Implies "allow_other".
|
||||
|
||||
This is rarely desired behavior: One should *usually* run gocryptfs as the
|
||||
account which owns the backing-store files, which should *usually* be one and
|
||||
the same with the account intended to access the decrypted content. An example
|
||||
of a case where this may be useful is a situation where content is stored on a
|
||||
filesystem that doesn't properly support UNIX ownership and permissions.
|
||||
|
||||
#### -forcedecode
|
||||
Force decode of encrypted files even if the integrity check fails, instead of
|
||||
failing with an IO error. Warning messages are still printed to syslog if corrupted
|
||||
files are encountered.
|
||||
It can be useful to recover files from disks with bad sectors or other corrupted
|
||||
media. It shall not be used if the origin of corruption is unknown, specially
|
||||
if you want to run executable files.
|
||||
|
||||
For corrupted media, note that you probably want to use dd_rescue(1)
|
||||
instead, which will recover all but the corrupted 4kB block.
|
||||
|
||||
This option makes no sense in reverse mode. It requires gocryptfs to be compiled with openssl
|
||||
support and implies -openssl true. Because of this, it is not compatible with -aessiv,
|
||||
that uses built-in Go crypto.
|
||||
|
||||
Setting this option forces the filesystem to read-only and noexec.
|
||||
|
||||
#### -fsname string
|
||||
Override the filesystem name (first column in df -T). Can also be
|
||||
passed as "-o fsname=" and is equivalent to libfuse's option of the
|
||||
same name. By default, CIPHERDIR is used.
|
||||
|
||||
#### -fusedebug
|
||||
Enable fuse library debug output.
|
||||
|
||||
#### -i duration, -idle duration
|
||||
Only for forward mode: automatically unmount the filesystem if it has been idle
|
||||
for the specified duration. Durations can be specified like "500s" or "2h45m".
|
||||
0 (the default) means stay mounted indefinitely.
|
||||
|
||||
When a process has open files or its working directory in the mount,
|
||||
this will keep it not idle indefinitely.
|
||||
|
||||
#### -kernel_cache
|
||||
Enable the kernel_cache option of the FUSE filesystem, see fuse(8) for details.
|
||||
|
||||
#### -ko
|
||||
Pass additional mount options to the kernel (comma-separated list).
|
||||
FUSE filesystems are mounted with "nodev,nosuid" by default. If gocryptfs
|
||||
runs as root, you can enable device files by passing the opposite mount option,
|
||||
"dev", and if you want to enable suid-binaries, pass "suid".
|
||||
"ro" (equivalent to passing the "-ro" option) and "noexec" may also be
|
||||
interesting. For a complete list see the section
|
||||
`FILESYSTEM-INDEPENDENT MOUNT OPTIONS` in mount(8). On MacOS, "local",
|
||||
"noapplexattr", "noappledouble" may be interesting.
|
||||
|
||||
Note that unlike "-o", "-ko" is a regular option and must be passed BEFORE
|
||||
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.
|
||||
|
||||
#### -nodev
|
||||
See `-dev, -nodev`.
|
||||
|
||||
#### -noexec
|
||||
See `-exec, -noexec`.
|
||||
|
||||
#### -nofail
|
||||
Having the `nofail` option in `/etc/fstab` instructs `systemd` to continue
|
||||
booting normally even if the mount fails (see `man systemd.fstab`).
|
||||
|
||||
The option is ignored by `gocryptfs` itself and has no effect outside `/etc/fstab`.
|
||||
|
||||
#### -nonempty
|
||||
Allow mounting over non-empty directories. FUSE by default disallows
|
||||
this to prevent accidental shadowing of files.
|
||||
|
||||
#### -noprealloc
|
||||
Disable preallocation before writing. By default, gocryptfs
|
||||
preallocates the space the next write will take using fallocate(2)
|
||||
in mode FALLOC_FL_KEEP_SIZE. The preallocation makes sure it cannot
|
||||
run out of space in the middle of the write, which would cause the
|
||||
last 4kB block to be corrupt and unreadable.
|
||||
|
||||
On ext4, preallocation is fast and does not cause a
|
||||
noticeable performance hit. Unfortunately, on Btrfs, preallocation
|
||||
is very slow, especially on rotational HDDs. The "-noprealloc"
|
||||
option gives users the choice to trade robustness against
|
||||
out-of-space errors for a massive speedup.
|
||||
|
||||
For benchmarks and more details of the issue see
|
||||
https://github.com/rfjakob/gocryptfs/issues/63 .
|
||||
|
||||
#### -nosuid
|
||||
See `-suid, -nosuid`.
|
||||
|
||||
#### -notifypid int
|
||||
Send USR1 to the specified process after successful mount. This is
|
||||
used internally for daemonization.
|
||||
|
||||
#### -rw, -ro
|
||||
Mount the filesystem read-write (`-rw`, default) or read-only (`-ro`).
|
||||
If both are specified, `-ro` takes precedence.
|
||||
|
||||
#### -reverse
|
||||
See the `-reverse` section in INIT FLAGS. You need to specifiy the
|
||||
`-reverse` option both at `-init` and at mount.
|
||||
|
||||
#### -serialize_reads
|
||||
The kernel usually submits multiple concurrent reads to service
|
||||
userspace requests and kernel readahead. gocryptfs serves them
|
||||
concurrently and in arbitrary order. On backing storage that performs
|
||||
poorly for concurrent or out-of-order reads (like Amazon Cloud Drive),
|
||||
this behavior can cause very slow read speeds.
|
||||
|
||||
The `-serialize_reads`
|
||||
option does two things: (1) reads will be submitted one-by-one (no
|
||||
concurrency) and (2) gocryptfs tries to order the reads by file
|
||||
offset order.
|
||||
|
||||
The ordering requires gocryptfs to wait a certain time before
|
||||
submitting a read. The serialization introduces extra locking.
|
||||
These factors will limit throughput to below 70MB/s.
|
||||
|
||||
For more details visit https://github.com/rfjakob/gocryptfs/issues/92 .
|
||||
|
||||
#### -sharedstorage
|
||||
Enable work-arounds so gocryptfs works better when the backing
|
||||
storage directory is concurrently accessed by multiple gocryptfs
|
||||
instances.
|
||||
|
||||
At the moment, it does two things:
|
||||
|
||||
1. Disable stat() caching so changes to the backing storage show up
|
||||
immediately.
|
||||
2. Disable hard link tracking, as the inode numbers on the backing
|
||||
storage are not stable when files are deleted and re-created behind
|
||||
our back. This would otherwise produce strange "file does not exist"
|
||||
and other errors.
|
||||
|
||||
When "-sharedstorage" is active, performance is reduced and hard
|
||||
links cannot be created.
|
||||
|
||||
Even with this flag set, you may hit occasional problems. Running
|
||||
gocryptfs on shared storage does not receive as much testing as the
|
||||
usual (exclusive) use-case. Please test your workload in advance
|
||||
and report any problems you may hit.
|
||||
|
||||
More info: https://github.com/rfjakob/gocryptfs/issues/156
|
||||
|
||||
#### -suid, -nosuid
|
||||
Enable (`-suid`) or disable (`-nosuid`) suid and sgid executables in a gocryptfs
|
||||
mount (default: `-nosuid`). If both are specified, `-nosuid` takes precedence.
|
||||
You need root permissions to use `-suid`.
|
||||
|
||||
#### -zerokey
|
||||
Use all-zero dummy master key. This options is only intended for
|
||||
automated testing as it does not provide any security.
|
||||
|
||||
COMMON OPTIONS
|
||||
==============
|
||||
|
||||
Options that apply to more than one action are listed below.
|
||||
Each options lists where it is applicable. Again, usually you
|
||||
don't need any.
|
||||
|
||||
#### -config string
|
||||
Use specified config file instead of `CIPHERDIR/gocryptfs.conf`.
|
||||
|
||||
Applies to: all actions that use a config file: mount, `-fsck`, `-passwd`, `-info`, `-init`.
|
||||
|
||||
#### -cpuprofile string
|
||||
Write cpu profile to specified file.
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### -d, -debug
|
||||
Enable debug output.
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### -extpass CMD [-extpass ARG1 ...]
|
||||
Use an external program (like ssh-askpass) for the password prompt.
|
||||
The program should return the password on stdout, a trailing newline is
|
||||
stripped by gocryptfs. If you just want to read from a password file, see `-passfile`.
|
||||
|
||||
When `-extpass` is specified once, the string argument will be split on spaces.
|
||||
For example, `-extpass "md5sum my password.txt"` will be executed as
|
||||
`"md5sum" "my" "password.txt"`, which is NOT what you want.
|
||||
|
||||
Specify `-extpass` twice or more to use the string arguments as-is.
|
||||
For example, you DO want to call `md5sum` like this:
|
||||
`-extpass "md5sum" -extpass "my password.txt"`.
|
||||
|
||||
If you want to prevent splitting on spaces but don't want to pass arguments
|
||||
to your program, use `"--"`, which is accepted by most programs:
|
||||
`-extpass "my program" -extpass "--"`
|
||||
|
||||
Applies to: all actions that ask for a password.
|
||||
|
||||
#### -fido2 DEVICE_PATH
|
||||
Use a FIDO2 token to initialize and unlock the filesystem.
|
||||
Use "fido2-token -L" to obtain the FIDO2 token device path.
|
||||
|
||||
Applies to: all actions that ask for a password.
|
||||
|
||||
#### -masterkey string
|
||||
Use a explicit master key specified on the command line or, if the special
|
||||
value "stdin" is used, read the masterkey from stdin, instead of reading
|
||||
the config file and asking for the decryption password.
|
||||
|
||||
Note that the command line, and with it the master key, is visible to
|
||||
anybody on the machine who can execute "ps -auxwww". Use "-masterkey=stdin"
|
||||
to avoid that risk.
|
||||
|
||||
The masterkey option is meant as a recovery option for emergencies, such as
|
||||
if you have forgotten the password or lost the config file.
|
||||
|
||||
Even if a config file exists, it will not be used. All non-standard
|
||||
settings have to be passed on the command line: `-aessiv` when you
|
||||
mount a filesystem that was created using reverse mode, or
|
||||
`-plaintextnames` for a filesystem that was created with that option.
|
||||
|
||||
Examples:
|
||||
|
||||
-masterkey=6f717d8b-6b5f8e8a-fd0aa206-778ec093-62c5669b-abd229cd-241e00cd-b4d6713d
|
||||
-masterkey=stdin
|
||||
|
||||
Applies to: all actions that ask for a password.
|
||||
|
||||
#### -memprofile string
|
||||
Write memory profile to the specified file. This is useful when debugging
|
||||
memory usage of gocryptfs.
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### -o COMMA-SEPARATED-OPTIONS
|
||||
For compatibility with mount(1), options are also accepted as
|
||||
"-o COMMA-SEPARATED-OPTIONS" at the end of the command line.
|
||||
For example, "-o q,zerokey" is equivalent to passing "-q -zerokey".
|
||||
|
||||
Note that you can only use options that are understood by gocryptfs
|
||||
with "-o". If you want to pass special flags to the kernel, you should
|
||||
use "-ko" (*k*ernel *o*ption). This is different in libfuse-based
|
||||
filesystems, that automatically pass any "-o" options they do not
|
||||
understand along to the kernel.
|
||||
|
||||
Example:
|
||||
|
||||
gocryptfs /tmp/foo /tmp/bar -o q,zerokey
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### -openssl bool/"auto"
|
||||
Use OpenSSL instead of built-in Go crypto (default "auto"). Using
|
||||
built-in crypto is 4x slower unless your CPU has AES instructions and
|
||||
you are using Go 1.6+. In mode "auto", gocrypts chooses the faster
|
||||
option.
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### -passfile FILE [-passfile FILE2 ...]
|
||||
Read password from the specified plain text file. The file should contain exactly
|
||||
one line (do not use binary files!).
|
||||
A warning will be printed if there is more than one line, and only
|
||||
the first line will be used. A single
|
||||
trailing newline is allowed and does not cause a warning.
|
||||
|
||||
Pass this option multiple times to read the first line from multiple
|
||||
files. They are concatenated for the effective password.
|
||||
|
||||
Example:
|
||||
|
||||
echo hello > hello.txt
|
||||
echo word > world.txt
|
||||
gocryptfs -passfile hello.txt -passfile world.txt
|
||||
|
||||
The effective password will be "helloworld".
|
||||
|
||||
Applies to: all actions that ask for a password.
|
||||
|
||||
#### -q, -quiet
|
||||
Quiet - silence informational messages.
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### -trace string
|
||||
Write execution trace to file. View the trace using "go tool trace FILE".
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### -wpanic
|
||||
When encountering a warning, panic and exit immediately. This is
|
||||
useful in regression testing.
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
#### \-\-
|
||||
Stop option parsing. Helpful when CIPHERDIR may start with a
|
||||
dash "-".
|
||||
|
||||
Applies to: all actions.
|
||||
|
||||
EXCLUDING FILES
|
||||
===============
|
||||
|
||||
In reverse mode, it is possible to exclude files from the encrypted view, using
|
||||
the `-exclude`, `-exclude-wildcard` and `-exclude-from` options.
|
||||
|
||||
`-exclude` matches complete paths, so `-exclude file.txt` only excludes a file
|
||||
named `file.txt` in the root of the mounted filesystem; files named `file.txt`
|
||||
in subdirectories are still visible. (This option is kept for compatibility
|
||||
with the behavior up to version 1.6.x)
|
||||
|
||||
`-exclude-wildcard` matches files anywhere, so `-exclude-wildcard file.txt`
|
||||
excludes files named `file.txt` in any directory. If you want to match complete
|
||||
paths, you can prefix the filename with a `/`: `-exclude-wildcard /file.txt`
|
||||
excludes only `file.txt` in the root of the mounted filesystem.
|
||||
|
||||
If there are many exclusions, you can use `-exclude-from` to read exclusion
|
||||
patterns from a file. The syntax is that of `-exclude-wildcard`, so use a
|
||||
leading `/` to match complete paths.
|
||||
|
||||
The rules for exclusion are that of [gitignore](https://git-scm.com/docs/gitignore#_pattern_format).
|
||||
In short:
|
||||
|
||||
1. A blank line matches no files, so it can serve as a separator
|
||||
for readability.
|
||||
2. A line starting with `#` serves as a comment. Put a backslash (`\`)
|
||||
in front of the first hash for patterns that begin with a hash.
|
||||
3. Trailing spaces are ignored unless they are quoted with backslash (`\`).
|
||||
4. An optional prefix `!` negates the pattern; any matching file
|
||||
excluded by a previous pattern will become included again. It is not
|
||||
possible to re-include a file if a parent directory of that file is
|
||||
excluded. Put a backslash (`\`) in front of the first `!` for
|
||||
patterns that begin with a literal `!`, for example, `\!important!.txt`.
|
||||
5. If the pattern ends with a slash, it is removed for the purpose of the
|
||||
following description, but it would only find a match with a directory.
|
||||
In other words, `foo/` will match a directory foo and paths underneath it,
|
||||
but will not match a regular file or a symbolic link foo.
|
||||
6. If the pattern does not contain a slash `/`, it is treated as a shell glob
|
||||
pattern and checked for a match against the pathname relative to the
|
||||
root of the mounted filesystem.
|
||||
7. Otherwise, the pattern is treated as a shell glob suitable for
|
||||
consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the
|
||||
pattern will not match a `/` in the pathname. For example,
|
||||
`Documentation/*.html` matches `Documentation/git.html` but not
|
||||
`Documentation/ppc/ppc.html` or `tools/perf/Documentation/perf.html`.
|
||||
8. A leading slash matches the beginning of the pathname. For example,
|
||||
`/*.c` matches `cat-file.c` but not `mozilla-sha1/sha1.c`.
|
||||
9. Two consecutive asterisks (`**`) in patterns matched against full
|
||||
pathname may have special meaning:
|
||||
i. A leading `**` followed by a slash means match in all directories.
|
||||
For example, `**/foo` matches file or directory `foo` anywhere,
|
||||
the same as pattern `foo`. `**/foo/bar` matches file or directory
|
||||
`bar` anywhere that is directly under directory `foo`.
|
||||
ii. A trailing `/**` matches everything inside. For example, `abc/**`
|
||||
matches all files inside directory `abc`, with infinite depth.
|
||||
iii. A slash followed by two consecutive asterisks then a slash matches
|
||||
zero or more directories. For example, `a/**/b` matches `a/b`,
|
||||
`a/x/b`, `a/x/y/b` and so on.
|
||||
iv. Other consecutive asterisks are considered invalid.
|
||||
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
|
||||
### Init
|
||||
|
||||
Create an encrypted filesystem in directory "mydir.crypt", mount it on "mydir":
|
||||
|
||||
mkdir mydir.crypt mydir
|
||||
gocryptfs -init mydir.crypt
|
||||
gocryptfs mydir.crypt mydir
|
||||
|
||||
### Mount
|
||||
|
||||
Mount an encrypted view of joe's home directory using reverse mode:
|
||||
|
||||
mkdir /home/joe.crypt
|
||||
gocryptfs -init -reverse /home/joe
|
||||
gocryptfs -reverse /home/joe /home/joe.crypt
|
||||
|
||||
### fstab
|
||||
|
||||
Adding this line to `/etc/fstab` will mount `/tmp/cipher` to `/tmp/plain` on boot, using the
|
||||
password in `/tmp/passfile`. Use `sudo mount -av` to test the line without having
|
||||
to reboot. Adjust the gocryptfs path acc. to the output of the command `which gocryptfs`.
|
||||
Do use the `nofail` option to prevent an unbootable system if the gocryptfs mount fails (see
|
||||
the `-nofail` option for details).
|
||||
|
||||
/tmp/cipher /tmp/plain fuse./usr/local/bin/gocryptfs nofail,allow_other,passfile=/tmp/password 0 0
|
||||
|
||||
EXIT CODES
|
||||
==========
|
||||
|
||||
0: success
|
||||
6: CIPHERDIR is not an empty directory (on "-init")
|
||||
10: MOUNTPOINT is not an empty directory
|
||||
12: password incorrect
|
||||
22: password is empty (on "-init")
|
||||
23: could not read gocryptfs.conf
|
||||
24: could not write gocryptfs.conf (on "-init" or "-password")
|
||||
26: fsck found errors
|
||||
other: please check the error message
|
||||
|
||||
See also: https://github.com/rfjakob/gocryptfs/blob/master/internal/exitcodes/exitcodes.go
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
mount(2) fuse(8) fallocate(2) encfs(1)
|
@ -1 +0,0 @@
|
||||
This page has been moved to https://nuetzlich.net/gocryptfs/security/ .
|
File diff suppressed because one or more lines are too long
@ -1,36 +0,0 @@
|
||||
ls: cannot access foo: No such file or directory
|
||||
ls: cannot access foo: No such file or directory
|
||||
ls: cannot access foo: No such file or directory
|
||||
ls: cannot access foo: No such file or directory
|
||||
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
|
||||
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
|
||||
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
|
||||
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
|
||||
|
||||
|
||||
u1026@d8min:/mnt/synology/public/tmp/g1$ strace -e lstat -p 8899 -f
|
||||
Process 8899 attached with 10 threads
|
||||
2017/11/11 18:12:21 Dispatch 238: LOOKUP, NodeId: 1. names: [foo] 4 bytes
|
||||
[pid 10539] lstat("/mnt/synology/public/tmp/g1/a/4DZNVle_txclugO7n_FRIg", 0xc4241adbe8) = -1 ENOENT (No such file or directory)
|
||||
2017/11/11 18:12:21 Serialize 238: LOOKUP code: OK value: {NodeId: 0 Generation=0 EntryValid=1.000 AttrValid=0.000 Attr={M00 SZ=0 L=0 0:0 B0*0 i0:0 A 0.000000000 M 0.000000000 C 0.000000000}}
|
||||
2017/11/11 18:12:22 Dispatch 239: LOOKUP, NodeId: 1. names: [foo] 4 bytes
|
||||
[pid 8903] lstat("/mnt/synology/public/tmp/g1/a/Xsy8mhdcIh0u9aiI7-iLiw", {st_mode=S_IFREG|0777, st_size=0, ...}) = 0
|
||||
2017/11/11 18:12:22 Serialize 239: LOOKUP code: OK value: {NodeId: 3 Generation=4 EntryValid=1.000 AttrValid=1.000 Attr={M0100777 SZ=0 L=1 1026:100 B0*16384 i0:36962337 A 1510419642.457639700 M 1510419642.457639700 C 1510419702.353712800}}
|
||||
|
||||
|
||||
Call Trace:
|
||||
|
||||
nodefs/fsops.go (c *rawBridge) Lookup
|
||||
nodefs/fsops.go (c *FileSystemConnector) internalLookup
|
||||
nodefs/inode.go (n *Inode) GetChild
|
||||
pathfs/pathfs.go (n *pathInode) GetAttr
|
||||
pathfs/pathfs.go (n *pathInode) GetPath
|
||||
nodefs/inode.go (n *Inode) Parent()
|
||||
pathfs/loopback.go (fs *loopbackFileSystem) GetAttr
|
||||
|
||||
Call Trace 2 (new child):
|
||||
|
||||
nodefs/fsops.go (c *rawBridge) Lookup
|
||||
nodefs/fsops.go (c *FileSystemConnector) internalLookup
|
||||
pathfs/pathfs.go (n *pathInode) Lookup
|
||||
pathfs/pathfs.go (n *pathInode) findChild
|
@ -1,56 +0,0 @@
|
||||
# extractloop.bash results
|
||||
|
||||
Memory usage stabilises at 141MiB, we do not run out of fds,
|
||||
and the iteration time is stable around 38 seconds:
|
||||
|
||||
![](extractloop_plot_csv.png)
|
||||
|
||||
What the extractloop stress test does is (top comment in `tests/stress_tests/extractloop.bash`):
|
||||
|
||||
```
|
||||
# Mount a gocryptfs filesystem somewhere on /tmp, then run two parallel
|
||||
# infinite loops inside that do the following:
|
||||
# 1) Extract linux-3.0.tar.gz
|
||||
# 2) Verify the md5sums
|
||||
# 3) Delete, go to (1)
|
||||
#
|
||||
# This test is good at discovering inode-related memory leaks because it creates
|
||||
# huge numbers of files.
|
||||
```
|
||||
|
||||
Test output (trimmed for brevity):
|
||||
```
|
||||
~/go/src/github.com/rfjakob/gocryptfs/tests/stress_tests$ ./extractloop.bash
|
||||
|
||||
20803 (process ID) old priority 0, new priority 19
|
||||
Testing gocryptfs
|
||||
Test dir: /tmp/extractloop_tmpdir/SMc
|
||||
'/tmp/extractloop.csv' -> '/tmp/extractloop_tmpdir/SMc.csv'
|
||||
[looper 2] Starting
|
||||
[looper 1] Starting
|
||||
[looper 2] Iteration 1 done, 42 seconds, RSS 36020 kiB
|
||||
[looper 1] Iteration 1 done, 42 seconds, RSS 36020 kiB
|
||||
[looper 2] Iteration 2 done, 40 seconds, RSS 45400 kiB
|
||||
[looper 1] Iteration 2 done, 40 seconds, RSS 45400 kiB
|
||||
[looper 1] Iteration 3 done, 40 seconds, RSS 53396 kiB
|
||||
[looper 2] Iteration 3 done, 40 seconds, RSS 53396 kiB
|
||||
[looper 1] Iteration 4 done, 39 seconds, RSS 64588 kiB
|
||||
[looper 2] Iteration 4 done, 40 seconds, RSS 64588 kiB
|
||||
[looper 1] Iteration 5 done, 40 seconds, RSS 64588 kiB
|
||||
[looper 2] Iteration 5 done, 39 seconds, RSS 64588 kiB
|
||||
[looper 1] Iteration 6 done, 39 seconds, RSS 71628 kiB
|
||||
[...]
|
||||
[looper 1] Iteration 945 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 2] Iteration 946 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 1] Iteration 946 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 1] Iteration 947 done, 37 seconds, RSS 140832 kiB
|
||||
[looper 2] Iteration 947 done, 37 seconds, RSS 140832 kiB
|
||||
[looper 1] Iteration 948 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 2] Iteration 948 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 1] Iteration 949 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 2] Iteration 949 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 1] Iteration 950 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 2] Iteration 950 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 1] Iteration 951 done, 38 seconds, RSS 140832 kiB
|
||||
[looper 2] Iteration 951 done, 38 seconds, RSS 140832 kiB
|
||||
```
|
Binary file not shown.
Before Width: | Height: | Size: 46 KiB |
@ -1,39 +0,0 @@
|
||||
File Format
|
||||
===========
|
||||
|
||||
Header
|
||||
|
||||
2 bytes header version (big endian uint16, currently 2)
|
||||
16 bytes file id
|
||||
|
||||
Data block, default AES-GCM mode
|
||||
|
||||
16 bytes GCM IV (nonce)
|
||||
1-4096 bytes encrypted data
|
||||
16 bytes GHASH
|
||||
|
||||
Data block, AES-SIV mode (used in reverse mode, or when explicitly enabled with `-init -aessiv`)
|
||||
|
||||
16 bytes nonce
|
||||
16 bytes SIV
|
||||
1-4096 bytes encrypted data
|
||||
|
||||
Full block overhead = 32/4096 = 1/128 = 0.78125 %
|
||||
|
||||
Example: 1-byte file
|
||||
--------------------
|
||||
|
||||
Header 18 bytes
|
||||
Data block 33 bytes
|
||||
|
||||
Total: 51 bytes
|
||||
|
||||
|
||||
Example: 5000-byte file
|
||||
-----------------------
|
||||
|
||||
Header 18 bytes
|
||||
Data block 4128 bytes
|
||||
Data block 936 bytes
|
||||
|
||||
Total: 5082 bytes
|
Binary file not shown.
Before Width: | Height: | Size: 53 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.2 KiB |
@ -1,10 +0,0 @@
|
||||
Results from benchmark-reverse.bash
|
||||
|
||||
VERSION LS CAT ENV
|
||||
------- --- ---- ---
|
||||
v1.3 2.1 19.9 go1.9.2
|
||||
v1.4 2.1 18.2
|
||||
v1.5 3.4 19.6 go1.10.3, Linux 4.17.12
|
||||
v1.6 3.6 19.9 go1.10.3, Linux 4.17.12
|
||||
|
||||
(seconds)
|
@ -1,86 +0,0 @@
|
||||
Tests of gocryptfs v1.7 and later are run on an
|
||||
Intel Core i5-3470 CPU (quad-core Ivy Bridge, AES-NI supported).
|
||||
Earlier tests on a Pentium G630 (Dual-core Sandy Bridge, no AES-NI).
|
||||
|
||||
The working directory is on tmpfs.
|
||||
The untar test uses https://cdn.kernel.org/pub/linux/kernel/v3.0/linux-3.0.tar.gz .
|
||||
The archive is placed on tmpfs as well.
|
||||
|
||||
WRITE: dd if=/dev/zero of=zero bs=131072 count=2000
|
||||
READ: dd if=zero of=/dev/null bs=131072 count=2000
|
||||
UNTAR: time tar xzf ../linux-3.0.tar.gz
|
||||
MD5: time md5sum --quiet -c linux-3.0.md5sums
|
||||
LS: time ls -lR linux-3.0 > /dev/null
|
||||
RM: time rm -Rf linux-3.0
|
||||
|
||||
(or just run benchmark.bash)
|
||||
|
||||
VERSION WRITE READ UNTAR MD5 LS RM ENV CHANGE? COMMIT MSG
|
||||
**********************
|
||||
* CPU = Pentium G630 *
|
||||
**********************
|
||||
v0.4 48 1.5 5
|
||||
v0.5-rc1 56 7 19
|
||||
v0.5-rc1-1 54 4.1 9
|
||||
v0.5-rc1-2 45 1.7 3.4
|
||||
v0.6 47 1.8 4.3
|
||||
v0.7 43 1.7 4.3
|
||||
v0.7.2 26 1.8 4.3
|
||||
v0.8 23 1.8 4.3
|
||||
v0.9-rc2 94 24 1.8 4.5
|
||||
v0.9 94 24 1.8 4.5
|
||||
v0.11 104 22 1.7 4.5
|
||||
v1.1 104 20 1.5 3.4 go1.7.1
|
||||
v1.1.1-34 112 22 1.5 3.6 go1.7.3
|
||||
v1.2.1-33 112 21 12 1.6 4.4 go1.8
|
||||
-serialize_reads 116 21 39 1.5 4.4 (v1.2.1-33 with -serialize_reads)
|
||||
v1.3-27 113 20 11 1.4 4.2
|
||||
v1.3-53-gf44902a 119 19 12 1.6 4.1
|
||||
v1.3-64-g80516ed 123 19 11 1.3 4.2
|
||||
v1.3-67-g9837cb0 125 19 11 1.4 4.2 go1.8.3, Linux 4.10
|
||||
v1.3-69-ge52594d 145 19.0 11.6 1.4 4.1
|
||||
v1.4-1-g3c6fe98 154 17.2 11.7 1.4 4.1
|
||||
v1.4-5-g0cc6f53 182 144 16.7 11.1 1.3 3.3
|
||||
v1.4-8-g80676c6 178 148 16.1 11.0 1.3 4.0
|
||||
v1.4-14-g9f4bd76 182 286 15.4 7.5 1.3 4.1
|
||||
v1.4-45-gd5671b7 183 282 14.9 7.3 1.1 2.9
|
||||
v1.4-45-gd5671b7 252 285 15.5 7.2 1.1 2.9 go1.8.3, Linux 4.11
|
||||
v1.4.1 253 285 16.0 7.4 1.3 3.0 go1.9, Linux 4.12.5
|
||||
v1.4.1-6-g276567e 258 289 16.1 7.5 1.3 3.0
|
||||
v1.5 228 292 17.6 9.3 1.5 3.5 go1.10.2, Linux 4.16.8
|
||||
v1.6 250 289 17.7 8.0 1.3 3.2 go1.10.3, Linux 4.17.12
|
||||
v1.7-beta1 229 278 17.1 8.8 1.7 3.2 go1.11.4, Linux 4.19.12
|
||||
v1.7-rc1 226 289 17.6 8.9 1.7 2.9
|
||||
********************************************
|
||||
* CPU = Core i5-3470, governor = powersave *
|
||||
********************************************
|
||||
v1.7 232 698 12.2 9.4 1.7 4.3 go1.12.9, Linux 5.2.17
|
||||
v1.7.1 450 697 11.5 9.5 1.5 3.6
|
||||
**********************************************
|
||||
* CPU = Core i5-3470, governor = performance *
|
||||
**********************************************
|
||||
v1.7.1 556 1000 9.0 4.2 0.9 2.0 go1.13.6, Linux 5.4.17
|
||||
v1.7.1 577 1100 8.3 4.2 0.9 2.0 go1.14.2, Linux 5.6.7
|
||||
v1.7.1-60-gb23f77c 472 1100 12.7 4.2 0.8 2.0
|
||||
v1.8.0 410 1000 17.5 6.7 5.4 7.8 go1.15.3, Linux 5.8.13
|
||||
v2.0-beta1 387 1100 36.2 14.4 12.8 19.3
|
||||
v2.0-beta1-5-gc943ed3 417 1000 30.4 12.7 9.9 16.4
|
||||
v2.0-beta1-6 529 1100 17.5 9.0 3.6 9.0
|
||||
v2.0-beta1-9-g029e44d 477 1000 15.5 8.7 2.8 7.6
|
||||
v2.0-beta2-16-geaca820 542 997 15.9 8.8 6.2 7.8 go1.16.2, Linux 5.11.10 fusefrontend: do not encrypt ACLs
|
||||
v2.0-beta2-36-g6aae2aa 505 1000 16.1 8.2 6.3 7.7
|
||||
v2.0-beta2-37-g24d5d39 558 1000 12.3 6.4 4.4 2.8 fs: add initial dirfd caching
|
||||
v2.0-beta2-42-g4a07d65 549 1000 8.2 4.7 1.8 2.4 fusefrontend: make dirCache work for "node itself"
|
||||
v2.0 420 1000 8.5 4.5 1.8 2.3 go1.16.5, Linux 5.11.21
|
||||
|
||||
Results for EncFS for comparison (benchmark.bash -encfs):
|
||||
|
||||
VERSION WRITE READ UNTAR MD5 LS RM
|
||||
**********************
|
||||
* CPU = Pentium G630 *
|
||||
**********************
|
||||
encfs v1.9.1 95 20 8 2.8 3.8
|
||||
**********************************************
|
||||
* CPU = Core i5-3470, governor = performance *
|
||||
**********************************************
|
||||
encfs v1.9.5 138 459 12.2 5.1 2.2 3.0
|
25
Makefile
25
Makefile
@ -1,25 +0,0 @@
|
||||
.phony: build
|
||||
build:
|
||||
./build.bash
|
||||
./Documentation/MANPAGE-render.bash
|
||||
|
||||
.phony: test
|
||||
test:
|
||||
./test.bash
|
||||
|
||||
.phony: root_test
|
||||
root_test:
|
||||
./build.bash
|
||||
cd tests/root_test && go test -c && sudo ./root_test.test -test.v
|
||||
|
||||
.phony: format
|
||||
format:
|
||||
go fmt ./...
|
||||
|
||||
.phony: install
|
||||
install:
|
||||
install -Dm755 -t "$(DESTDIR)/usr/bin/" gocryptfs
|
||||
install -Dm755 -t "$(DESTDIR)/usr/bin/" gocryptfs-xray/gocryptfs-xray
|
||||
install -Dm644 -t "$(DESTDIR)/usr/share/man/man1/" Documentation/gocryptfs.1
|
||||
install -Dm644 -t "$(DESTDIR)/usr/share/man/man1/" Documentation/gocryptfs-xray.1
|
||||
install -Dm644 -t "$(DESTDIR)/usr/share/licenses/gocryptfs" LICENSE
|
704
README.md
704
README.md
@ -1,700 +1,6 @@
|
||||
[![gocryptfs](Documentation/gocryptfs-logo.png)](https://nuetzlich.net/gocryptfs/)
|
||||
[![Build Status](https://travis-ci.org/rfjakob/gocryptfs.svg?branch=master)](https://travis-ci.org/rfjakob/gocryptfs)
|
||||
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/rfjakob/gocryptfs)](https://goreportcard.com/report/github.com/rfjakob/gocryptfs)
|
||||
[![Latest release](https://img.shields.io/github/release/rfjakob/gocryptfs.svg)](https://github.com/rfjakob/gocryptfs/releases)
|
||||
[![Homebrew version](https://img.shields.io/homebrew/v/gocryptfs.svg)](https://formulae.brew.sh/formula/gocryptfs#default)
|
||||
libgocryptfs is a re-desing of the original [gocryptfs](https://github.com/rfjakob/gocryptfs) code to work as a library. Volumes are not mounted with [FUSE](https://www.kernel.org/doc/html/latest/filesystems/fuse.html) but rather opened in memory and accessed through API calls. What the purpose ?
|
||||
- Allow the use of gocryptfs in embedded devices where FUSE is not available (such as Android)
|
||||
- Reduce attack surface by restricting volumes access to only one process rather than one user
|
||||
|
||||
An encrypted overlay filesystem written in Go.
|
||||
Official website: https://nuetzlich.net/gocryptfs ([markdown source](https://github.com/rfjakob/gocryptfs-website/blob/master/docs/index.md)).
|
||||
|
||||
![Folders side-by-side animation](Documentation/folders-side-by-side.gif)
|
||||
|
||||
gocryptfs is built on top the excellent
|
||||
[go-fuse](https://github.com/hanwen/go-fuse) FUSE library.
|
||||
This project was inspired by EncFS and strives to fix its security
|
||||
issues while providing good performance
|
||||
([benchmarks](https://nuetzlich.net/gocryptfs/comparison/#performance)).
|
||||
For details on the security of gocryptfs see the
|
||||
[Security](https://nuetzlich.net/gocryptfs/security/) design document.
|
||||
|
||||
All tags from v0.4 onward are signed by the *gocryptfs signing key*.
|
||||
Please check [Signed Releases](https://nuetzlich.net/gocryptfs/releases/)
|
||||
for details.
|
||||
|
||||
Current Status
|
||||
--------------
|
||||
|
||||
gocryptfs has reached version 1.0 on July 17, 2016. It has gone through
|
||||
hours and hours of stress (fsstress, extractloop.bash) and correctness
|
||||
testing (xfstests). It is now considered ready for general consumption.
|
||||
|
||||
The old principle still applies: Important data should have a backup.
|
||||
Also, keep a copy of your master key (printed on mount) in a safe place.
|
||||
This allows you to access the data even if the gocryptfs.conf config
|
||||
file is damaged or you lose the password.
|
||||
|
||||
The security of gocryptfs has been audited in March 3, 2017. The audit
|
||||
is available [here (defuse.ca)](https://defuse.ca/audits/gocryptfs.htm).
|
||||
|
||||
Platforms
|
||||
---------
|
||||
|
||||
Linux is gocryptfs' native platform.
|
||||
|
||||
Beta-quality Mac OS X support is available, which means most things work
|
||||
fine but you may hit an occasional problem. Check out
|
||||
[ticket #15](https://github.com/rfjakob/gocryptfs/issues/15) for the history
|
||||
of Mac OS X support but please create a new ticket if you hit a problem.
|
||||
|
||||
For Windows, an independent C++ reimplementation can be found here:
|
||||
[cppcryptfs](https://github.com/bailey27/cppcryptfs)
|
||||
|
||||
A standalone Python tool that can decrypt files & file names is here:
|
||||
[gocryptfs-inspect](https://github.com/slackner/gocryptfs-inspect)
|
||||
|
||||
Installation
|
||||
------------
|
||||
Precompiled binaries that work on all x86_64 Linux systems are available for download from the github releases page.
|
||||
|
||||
On Debian, gocryptfs is available as a deb package:
|
||||
```bash
|
||||
apt install gocryptfs
|
||||
```
|
||||
|
||||
On Mac OS X, gocryptfs is available as a Homebrew formula:
|
||||
```bash
|
||||
brew install gocryptfs
|
||||
```
|
||||
|
||||
On Fedora, gocryptfs is available as an rpm package:
|
||||
```bash
|
||||
sudo dnf install gocryptfs
|
||||
```
|
||||
|
||||
If you use the standalone binary, make sure you install the `fuse` package
|
||||
from your distributions package repository before running `gocryptfs`.
|
||||
|
||||
See the [Quickstart](https://nuetzlich.net/gocryptfs/quickstart/) page for more info.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
gocryptfs comes with is own test suite that is constantly expanded as features are
|
||||
added. Run it using `./test.bash`. It takes about 1 minute and requires FUSE
|
||||
as it mounts several test filesystems.
|
||||
|
||||
The `stress_tests` directory contains stress tests that run indefinitely.
|
||||
|
||||
In addition, I have ported `xfstests` to FUSE, the result is the
|
||||
[fuse-xfstests](https://github.com/rfjakob/fuse-xfstests) project. gocryptfs
|
||||
passes the "generic" tests with one exception, results: [XFSTESTS.md](Documentation/XFSTESTS.md)
|
||||
|
||||
A lot of work has gone into this. The testing has found bugs in gocryptfs
|
||||
as well as in the go-fuse library.
|
||||
|
||||
Compile
|
||||
-------
|
||||
|
||||
Install Go 1.11 or higher:
|
||||
|
||||
* Debian/Ubuntu: `apt install golang`
|
||||
* Fedora: `dnf install golang`
|
||||
|
||||
Then, download the source code and compile:
|
||||
|
||||
$ git clone https://github.com/rfjakob/gocryptfs.git
|
||||
$ cd gocryptfs
|
||||
$ ./build-without-openssl.bash
|
||||
|
||||
This will compile a static binary that uses the Go stdlib crypto backend.
|
||||
|
||||
If you want to use the OpenSSL crypto backend (faster on
|
||||
old CPUs lacking AES-NI), you have to install a few dependencies:
|
||||
|
||||
* Debian/Ubuntu: `apt install libssl-dev gcc pkg-config`
|
||||
* Fedora: `dnf install openssl-devel gcc pkg-config`
|
||||
|
||||
Then, run:
|
||||
|
||||
$ ./build.bash
|
||||
|
||||
Use
|
||||
---
|
||||
|
||||
$ mkdir cipher plain
|
||||
$ ./gocryptfs -init cipher
|
||||
$ ./gocryptfs cipher plain
|
||||
|
||||
See the [Quickstart](https://nuetzlich.net/gocryptfs/quickstart/) page for more info.
|
||||
|
||||
The [MANPAGE.md](Documentation/MANPAGE.md) describes all available command-line options.
|
||||
|
||||
Use: Reverse Mode
|
||||
-----------------
|
||||
|
||||
$ mkdir cipher plain
|
||||
$ ./gocryptfs -reverse -init plain
|
||||
$ ./gocryptfs -reverse plain cipher
|
||||
|
||||
Graphical Interface
|
||||
-------------------
|
||||
|
||||
The [SiriKali](https://mhogomchungu.github.io/sirikali/) project supports
|
||||
gocryptfs and runs on Linux and OSX.
|
||||
|
||||
[cppcryptfs](https://github.com/bailey27/cppcryptfs) on Windows provides
|
||||
its own GUI.
|
||||
|
||||
Stable CLI ABI
|
||||
--------------
|
||||
|
||||
If you want to call gocryptfs from your app or script, see
|
||||
[CLI_ABI.md](Documentation/CLI_ABI.md) for the official stable
|
||||
ABI. This ABI is regression-tested by the test suite.
|
||||
|
||||
Storage Overhead
|
||||
----------------
|
||||
|
||||
* Empty files take 0 bytes on disk
|
||||
* 18 byte file header for non-empty files (2 bytes version, 16 bytes random file id)
|
||||
* 32 bytes of storage overhead per 4kB block (16 byte nonce, 16 bytes auth tag)
|
||||
|
||||
[file-format.md](Documentation/file-format.md) contains a more detailed description.
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Since version 0.7.2, gocryptfs is as fast as EncFS in the default mode,
|
||||
and significantly faster than EncFS' "paranoia" mode that provides
|
||||
a security level comparable to gocryptfs.
|
||||
|
||||
On CPUs without AES-NI, gocryptfs uses OpenSSL through a thin wrapper called `stupidgcm`.
|
||||
This provides a 4x speedup compared to Go's builtin AES-GCM
|
||||
implementation. See [CPU-Benchmarks](https://github.com/rfjakob/gocryptfs/wiki/CPU-Benchmarks)
|
||||
for details, or run `gocryptfs -speed` to see the encryption performance of your CPU.
|
||||
Example for a CPU without AES-NI:
|
||||
|
||||
```
|
||||
$ ./gocryptfs -speed
|
||||
gocryptfs v2.0; go-fuse v2.1.1-0.20210508151621-62c5aa1919a7; 2021-06-06 go1.16.5 linux/amd64
|
||||
AES-GCM-256-OpenSSL 536.63 MB/s
|
||||
AES-GCM-256-Go 831.84 MB/s (selected in auto mode)
|
||||
AES-SIV-512-Go 155.85 MB/s
|
||||
XChaCha20-Poly1305-Go 700.02 MB/s (benchmark only, not selectable yet)
|
||||
```
|
||||
|
||||
You can run `./benchmark.bash` to run gocryptfs' canonical set of
|
||||
benchmarks that include streaming write, extracting a linux kernel
|
||||
tarball, recursively listing and finally deleting it. The output will
|
||||
look like this:
|
||||
|
||||
```
|
||||
$ ./benchmark.bash
|
||||
Testing gocryptfs at /tmp/benchmark.bash.xFD: gocryptfs v2.0; go-fuse v2.1.1-0.20210508151621-62c5aa1919a7; 2021-06-06 go1.16.5 linux/amd64
|
||||
WRITE: 262144000 bytes (262 MB, 250 MiB) copied, 0,698174 s, 375 MB/s
|
||||
READ: 262144000 bytes (262 MB, 250 MiB) copied, 0,268916 s, 975 MB/s
|
||||
UNTAR: 8,970
|
||||
MD5: 4,846
|
||||
LS: 1,851
|
||||
RM: 2,367
|
||||
```
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
v2.0.1, 2021-06-07
|
||||
* Fix symlink creation reporting the wrong size, causing git to report it as modified
|
||||
([#574](https://github.com/rfjakob/gocryptfs/issues/574))
|
||||
|
||||
v2.0, 2021-06-05
|
||||
* Fix a few [issues discovered by xfstests](https://github.com/rfjakob/fuse-xfstests/wiki/results_2021-05-19)
|
||||
* Biggest change: rewrite SEEK_HOLE / SEEK_DATA logic (now emulates 4k alignment)
|
||||
|
||||
v2.0-beta4, 2021-05-15
|
||||
* **Make ACLs *actually* work (pass `-acl` to enable)** ([#536](https://github.com/rfjakob/gocryptfs/issues/536))
|
||||
* Blocklist `RENAME_EXCHANGE` and `RENAME_WHITEOUT` (broken as discovered by [fuse-xfstest/gocryptfs-2019-12](https://github.com/rfjakob/fuse-xfstests/tree/gocryptfs-2019-12))
|
||||
|
||||
v2.0-beta3, 2021-04-24
|
||||
* MANPAGE: Split options into sections acc. to where they apply ([#517](https://github.com/rfjakob/gocryptfs/issues/517))
|
||||
* `-idle`: count cwd inside the mount as busy ([#533](https://github.com/rfjakob/gocryptfs/issues/533))
|
||||
* Make `gocryptfs.diriv` and `gocryptfs.xxx.name` files world-readable to make encrypted backups easier
|
||||
when mounting via [/etc/fstab](Documentation/MANPAGE.md#fstab) ([#539](https://github.com/rfjakob/gocryptfs/issues/539))
|
||||
* Make it work with MacFUSE v4.x ([#524](https://github.com/rfjakob/gocryptfs/issues/524))
|
||||
* **Disable ACL encryption**, it causes a lot of problems ([#543](https://github.com/rfjakob/gocryptfs/issues/543),
|
||||
[#536](https://github.com/rfjakob/gocryptfs/issues/536))
|
||||
* Old encrypted ACLs are reported by `gocryptfs -fsck` but otherwise ignored
|
||||
* This fixes inheritance, but does not yet enforce them correctly
|
||||
* Include `gocryptfs-xray` in binary releases ([#496](https://github.com/rfjakob/gocryptfs/issues/496))
|
||||
* go-fuse: track *most recent* parent. This improves robustness when the filesystem is modified behind
|
||||
the back of gocryptfs. Helps both with `-sharedstorage` and also without.
|
||||
([commit 1](https://github.com/hanwen/go-fuse/commit/c3186132bf8b7a04b5e5bc27489d88181f92e4e0),
|
||||
[commit 2](https://github.com/hanwen/go-fuse/commit/a90e1f463c3f172a7690a6449fe5955a180dfec3),
|
||||
[#549](https://github.com/rfjakob/gocryptfs/issues/549))
|
||||
* Add directory fd caching for 2x - 3x speed boost in small file ops compared to v2.0-beta2
|
||||
([performance numbers](https://github.com/rfjakob/gocryptfs/blob/5cb1e55714aa92a848c0fb5fc3fa7b91625210fe/Documentation/performance.txt#L73))
|
||||
|
||||
v2.0-beta2, 2020-11-14
|
||||
* Improve [performance](Documentation/performance.txt#L69)
|
||||
* Fix [GETATTR panic](https://github.com/rfjakob/gocryptfs/issues/519#issuecomment-718790790) in reverse mode
|
||||
|
||||
v2.0-beta1, 2020-10-15
|
||||
* **Switch to the improved go-fuse [v2 API](https://pkg.go.dev/github.com/hanwen/go-fuse/v2@v2.0.3/fs)**
|
||||
* This is a big change, a lot of code has been reorganized or rewritten
|
||||
to fit the v2 API model.
|
||||
* Please test & report bugs
|
||||
* No changes to the on-disk format
|
||||
* File descriptor caching is not yet implemented,
|
||||
causing a slowdown. Caching will be implemented for v2.0 final.
|
||||
* **Add support for FIDO2 tokens (`-fido2`, [#505](https://github.com/rfjakob/gocryptfs/pull/505))**
|
||||
* Add `-encrypt-paths` / `-decrypt-paths` functionality to `gocryptfs-xray`
|
||||
([#416](https://github.com/rfjakob/gocryptfs/issues/416))
|
||||
* Accept multiple `-passfile`s
|
||||
([#288](https://github.com/rfjakob/gocryptfs/issues/288))
|
||||
* Make `-masterkey=stdin` work together with `-passwd`
|
||||
([#461](https://github.com/rfjakob/gocryptfs/issues/461))
|
||||
* Fix `Unknown opcode 2016` crash on Google Cloud
|
||||
([go-fuse #276](https://github.com/hanwen/go-fuse/issues/276),
|
||||
[gocryptfs commit ec74d1d](https://github.com/rfjakob/gocryptfs/commit/ec74d1d2f4217a9a337d1db9902f32ae2aecaf33))
|
||||
|
||||
v1.8.0, 2020-05-09
|
||||
* Enable ACL support ([#453](https://github.com/rfjakob/gocryptfs/issues/453))
|
||||
* **Warning 2021-02-07**: This feature is incomplete! Do not use ACLs before gocryptfs v2.0 final!
|
||||
Reading and writing ACLs works, but they are not enforced or inherited ([#542](https://github.com/rfjakob/gocryptfs/issues/542))
|
||||
* Ignore `.nfsXXX` temporary files
|
||||
([#367](https://github.com/rfjakob/gocryptfs/issues/431))
|
||||
* Handle inode number collisions from multiple devices
|
||||
([#435](https://github.com/rfjakob/gocryptfs/issues/435))
|
||||
* Drop `-nonempty` for fusermount3
|
||||
([#440](https://github.com/rfjakob/gocryptfs/pull/440))
|
||||
* Reverse mode: improve inode number mapping and max=1000000000000000000 limitation
|
||||
([#457](https://github.com/rfjakob/gocryptfs/issues/457))
|
||||
* Enable `--buildmode=pie` ([#460](https://github.com/rfjakob/gocryptfs/pull/460))
|
||||
* Migrate from dep to Go Modules
|
||||
([commit cad711993](https://github.com/rfjakob/gocryptfs/commit/cad711993d67dd920f9749a09414dbbba6ab8136))
|
||||
* go mod: update dependencies
|
||||
([commit b23f77c](https://github.com/rfjakob/gocryptfs/commit/b23f77c8ead0dbb5ed59dd50e94f13aacf7dbaf1))
|
||||
* `gocryptfs -speed`: add XChaCha20-Poly1305-Go
|
||||
([#452](https://github.com/rfjakob/gocryptfs/issues/452))
|
||||
* Respect `GOMAXPROCS` environment variable
|
||||
([commit ff210a06f](https://github.com/rfjakob/gocryptfs/commit/ff210a06fb3097eecd5668ddb3ace9c76873eb00)
|
||||
* Completely remove Trezor-related code (commit 1364b44ae356da31e24e5605fe73a307e9d6fb03)
|
||||
* Has been disabled since v1.7 due to issues a third-party module.
|
||||
* Please use FIDO2 instead (gocryptfs v2.0)
|
||||
|
||||
v1.7.1, 2019-10-06
|
||||
* Support wild cards in reverse mode via `--exclude-wildcard`
|
||||
([#367](https://github.com/rfjakob/gocryptfs/pull/367)). Thanks @ekalin!
|
||||
* Create `gocryptfs.diriv` files with 0440 permissions to make it easier to
|
||||
share an encrypted folder via a network drive
|
||||
([#387](https://github.com/rfjakob/gocryptfs/issues/387)).
|
||||
Note: as a security precaution, the owner must still manually
|
||||
`chmod gocryptfs.conf 0440` to allow mounting.
|
||||
* Allow the `nofail` option in `/etc/fstab`
|
||||
* `-passwd` can now change the `-scryptn` parameter for existing filesystems
|
||||
([#400](https://github.com/rfjakob/gocryptfs/issues/400))
|
||||
* Fix `-idle` unmounting the filesystem despite recent activity
|
||||
([#421](https://github.com/rfjakob/gocryptfs/issues/421))
|
||||
* **Fix a race condition related to inode number reuse
|
||||
([#363](https://github.com/rfjakob/gocryptfs/issues/363))**.
|
||||
It could be triggered by concurrently creating and deleting files and can lead to data loss
|
||||
in the affected file. This bug was found by the automated tests on Travis
|
||||
and was very hard to trigger locally.
|
||||
* tests: use /var/tmp instead of /tmp by default
|
||||
([commit 8c4429](https://github.com/rfjakob/gocryptfs/commit/8c4429408716d9890a98a48c246d616dbfea7e31))
|
||||
|
||||
v1.7, 2019-03-17
|
||||
* **Fix possible symlink race attacks in forward mode** when using allow_other + plaintextnames
|
||||
* If you use *both* `-allow_other` *and* `-plaintextnames`, you should upgrade.
|
||||
Malicious users could trick gocryptfs into modifying files outside of `CIPHERDIR`,
|
||||
or reading files inside `CIPHERDIR` that they should not have access to.
|
||||
* If you do not use `-plaintextnames` (disabled per default), these attacks do
|
||||
not work as symlinks are encrypted.
|
||||
* Forward mode has been reworked to use the "\*at" family of system calls everywhere
|
||||
(`Openat/Unlinkat/Symlinkat/...`).
|
||||
* As a result, gocryptfs may run slightly slower, as the caching logic has been
|
||||
replaced and is very simple at the moment.
|
||||
* The possibility for such attacks was found during an internal code review.
|
||||
* Reverse mode: fix excluded, unaccessible files showing up in directory listings
|
||||
([#285](https://github.com/rfjakob/gocryptfs/issues/285),
|
||||
[#286](https://github.com/rfjakob/gocryptfs/issues/286))
|
||||
* gocryptfs-xray: add `-aessiv` flag for correctly parsing AES-SIV format files
|
||||
([#299](https://github.com/rfjakob/gocryptfs/issues/299))
|
||||
* Ensure that standard fds 0,1,2 are always initialized
|
||||
([#320](https://github.com/rfjakob/gocryptfs/issues/320)).
|
||||
Prevents trouble in the unlikely case that gocryptfs is called with
|
||||
stdin,stdout and/or stderr closed.
|
||||
* `-extpass` now can be specified multiple times to support arguments containing spaces
|
||||
([#289](https://github.com/rfjakob/gocryptfs/issues/289))
|
||||
* Drop Fstatat, Mkdirat, Syslinkat, Fchownat, Unlinkat, Renameat, Openat emulation of MacOS
|
||||
and instead use native functions (thanks @slackner !)
|
||||
* Use `Setreuid` to robustly set the owner with allow_other (@slackner,
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/03b9d65cce53fb95b7d489ecd03d0853b9b923fb)))
|
||||
* Pack the rendered man page into the source code archive for user convenience
|
||||
([issue 355](https://github.com/rfjakob/gocryptfs/issues/355))
|
||||
* Disable Trezor support again (commit 16fac26c57ba303bf60266d24c17f5243e5ea376)
|
||||
* Trezor support has been broken since Sept 2018 due to issues
|
||||
in a third-party module ([#261](https://github.com/rfjakob/gocryptfs/issues/261))
|
||||
|
||||
v1.6.1, 2018-12-12
|
||||
* Fix "Operation not supported" chmod errors on Go 1.11
|
||||
([#271](https://github.com/rfjakob/gocryptfs/issues/271))
|
||||
|
||||
v1.6, 2018-08-18
|
||||
* **Add `-e` / `-exclude` option** for reverse mode
|
||||
([#235](https://github.com/rfjakob/gocryptfs/issues/235),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/ec2fdc19cf9358ae7ba09c528a5807b6b0760f9b))
|
||||
* Add support for the Trezor One HSM [PR#247](https://github.com/rfjakob/gocryptfs/pull/247), thanks @xaionaro!
|
||||
* Use `./build.bash -tags enable_trezor` to compile with Trezor support
|
||||
* Then, use `gocryptfs -init -trezor` to create a filesystem locked with a physical Trezor device.
|
||||
* Note 2021-01-31: Support was removed again in gocryptfs v1.7. Please use `-fido2` in gocryptfs v2.0.
|
||||
* Only print master key once, on init
|
||||
([#76](https://github.com/rfjakob/gocryptfs/issues/76),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/6d64dfe8f7acd8e9ca4a659d26318e442c2db85a))
|
||||
* Fall back to buffered IO even when passed `O_DIRECT`
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/893e41149ed353f355047003b89eeff456990e76))
|
||||
|
||||
v1.5, 2018-06-12
|
||||
* **Support extended attributes (xattr)** in forward mode
|
||||
([#217](https://github.com/rfjakob/gocryptfs/issues/217)). Older gocryptfs versions
|
||||
will ignore the extended attributes.
|
||||
* **Add `-fsck` function**
|
||||
([#191](https://github.com/rfjakob/gocryptfs/issues/191))
|
||||
* Fix clobbered timestamps on MacOS High Sierra
|
||||
([#229](https://github.com/rfjakob/gocryptfs/issues/229))
|
||||
* Add `-masterkey=stdin` functionality
|
||||
([#218](https://github.com/rfjakob/gocryptfs/issues/218))
|
||||
* Accept `-dev`/`-nodev`, `suid`/`nosuid`, `-exec`/`-noexec`,
|
||||
`-ro`/`-rw` flags to make mounting via `/etc/fstab` possible.
|
||||
Thanks @mahkoh! ([#233](https://github.com/rfjakob/gocryptfs/pull/233),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/53d6a9999dd0e4c31636d16179f284fff35a35d9),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/10212d791a3196c2c8705a7a3cccdeb14a8efdbe))
|
||||
* Fix a `logger` path issue on SuSE
|
||||
[#225](https://github.com/rfjakob/gocryptfs/issues/225)
|
||||
* Stop printing the help text on a "flag provided but not defined"
|
||||
error ([commit](https://github.com/rfjakob/gocryptfs/commit/5ad26495fc86527bbfe75ac6b46528d49a373676))
|
||||
|
||||
v1.4.4, 2018-03-18
|
||||
* Overwrite secrets in memory with zeros as soon as possible
|
||||
([#211](https://github.com/rfjakob/gocryptfs/issues/211))
|
||||
* Fix Getdents problems on i386 and mips64le
|
||||
([#197](https://github.com/rfjakob/gocryptfs/issues/197),
|
||||
[#200](https://github.com/rfjakob/gocryptfs/issues/200))
|
||||
* Make building with gccgo work
|
||||
([#201](https://github.com/rfjakob/gocryptfs/issues/201))
|
||||
* MacOS: fix `osxfuse: vnode changed generation` / `Error code -36` issue in go-fuse
|
||||
([#213](https://github.com/rfjakob/gocryptfs/issues/213),
|
||||
[commit](https://github.com/hanwen/go-fuse/commit/a9ddcb8a4b609500fc59c89ccc9ee05f00a5fefd))
|
||||
* Fix various test issues on MacOS
|
||||
|
||||
v1.4.3, 2018-01-21
|
||||
* **Fix several symlink race attacks** in connection with reverse mode
|
||||
and allow_other. Thanks to @slackner for reporting and helping to fix
|
||||
the issues:
|
||||
* Fix symlink races in reverse mode
|
||||
([issue #165](https://github.com/rfjakob/gocryptfs/issues/165))
|
||||
* Fix symlink races in connection with `-allow_other`
|
||||
([issue #177](https://github.com/rfjakob/gocryptfs/issues/177))
|
||||
* Fix problems with special names when using `-plaintextnames`
|
||||
([issue #174](https://github.com/rfjakob/gocryptfs/issues/174))
|
||||
* Add `-devrandom` command-line option
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/f3c777d5eaa682d878c638192311e52f9c204294))
|
||||
* Add `-sharedstorage` command-line option
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/e36a0ebf189a826aaa63909c5518c16356f5f903),
|
||||
[issue #156](https://github.com/rfjakob/gocryptfs/issues/156))
|
||||
* MacOS: let OSXFuse create the mountpoint if it does not exist
|
||||
([issue #194](https://github.com/rfjakob/gocryptfs/issues/194))
|
||||
|
||||
v1.4.2, 2017-11-01
|
||||
* Add `Gopkg.toml` file for `dep` vendoring and reproducible builds
|
||||
([issue #142](https://github.com/rfjakob/gocryptfs/issues/142))
|
||||
* MacOS: deal with `.DS_Store` files inside CIPHERDIR
|
||||
([issue #140](https://github.com/rfjakob/gocryptfs/issues/140))
|
||||
* Reverse mode: fix ENOENT error affecting names exactly 176 bytes long
|
||||
([issue #143](https://github.com/rfjakob/gocryptfs/issues/143))
|
||||
* Support kernels compiled with > 128 kiB FUSE request size (Synology NAS)
|
||||
([issue #145](https://github.com/rfjakob/gocryptfs/issues/145),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/4954c87979efaf5b8184efccc7d9a38c21e4209b))
|
||||
* Fix a startup hang when `$PATH` contains the mountpoint
|
||||
([issue #146](https://github.com/rfjakob/gocryptfs/issues/146))
|
||||
|
||||
v1.4.1, 2017-08-21
|
||||
* **Use memory pools for buffer handling** (
|
||||
[3c6fe98](https://github.com/rfjakob/gocryptfs/commit/3c6fe98),
|
||||
[b2a23e9](https://github.com/rfjakob/gocryptfs/commit/b2a23e9),
|
||||
[12c0101](https://github.com/rfjakob/gocryptfs/commit/12c0101))
|
||||
* On my machine, this **doubles** the streaming read speed
|
||||
(see [performance.txt](https://github.com/rfjakob/gocryptfs/blob/v1.4.1/Documentation/performance.txt#L38))
|
||||
* Implement and use the getdents(2) syscall for a more efficient
|
||||
OpenDir implementation
|
||||
([e50a6a5](https://github.com/rfjakob/gocryptfs/commit/e50a6a5))
|
||||
* Purge masterkey from memory as soon as possible
|
||||
([issue #137](https://github.com/rfjakob/gocryptfs/issues/137))
|
||||
* Reverse mode: fix inode number collision between .name and .diriv
|
||||
files
|
||||
([d12aa57](https://github.com/rfjakob/gocryptfs/commit/d12aa57))
|
||||
* Prevent the logger from holding stdout open
|
||||
([issue #130](https://github.com/rfjakob/gocryptfs/issues/130))
|
||||
* MacOS: make testing without openssl work properly
|
||||
([ccf1a84](https://github.com/rfjakob/gocryptfs/commit/ccf1a84))
|
||||
* MacOS: specify a volume name
|
||||
([9f8e19b](https://github.com/rfjakob/gocryptfs/commit/9f8e19b))
|
||||
* Enable writing to write-only files
|
||||
([issue #125](https://github.com/rfjakob/gocryptfs/issues/125))
|
||||
|
||||
v1.4, 2017-06-20
|
||||
* **Switch to static binary releases**
|
||||
* From gocryptfs v1.4, I will only release statically-built binaries.
|
||||
These support all Linux distributions but cannot use OpenSSL.
|
||||
* OpenSSL is still supported - just compile from source!
|
||||
* Add `-force_owner` option to allow files to be presented as owned by a
|
||||
different user or group from the user running gocryptfs. Please see caveats
|
||||
and guidance in the man page before using this functionality.
|
||||
* Increase open file limit to 4096 ([#82](https://github.com/rfjakob/gocryptfs/issues/82)).
|
||||
* Implement path decryption via ctlsock ([#84](https://github.com/rfjakob/gocryptfs/issues/84)).
|
||||
Previously, decryption was only implemented for reverse mode. Now both
|
||||
normal and reverse mode support both decryption and encryption of
|
||||
paths via ctlsock.
|
||||
* Add more specific exit codes for the most common failure modes,
|
||||
documented in [CLI_ABI.md](Documentation/CLI_ABI.md)
|
||||
* Reverse mode: make sure hard-linked files always return the same
|
||||
ciphertext
|
||||
([commit 9ecf2d1a](https://github.com/rfjakob/gocryptfs/commit/9ecf2d1a3f69e3d995012073afe3fc664bd928f2))
|
||||
* Display a shorter, friendlier help text by default.
|
||||
* **Parallelize file content encryption** by splitting data blocks into two
|
||||
threads ([ticket#116](https://github.com/rfjakob/gocryptfs/issues/116))
|
||||
* Prefetch random nonces in the background
|
||||
([commit 80516ed](https://github.com/rfjakob/gocryptfs/commit/80516ed3351477793eec882508969b6b29b69b0a))
|
||||
* Add `-info` option to pretty-print infos about a filesystem.
|
||||
|
||||
v1.3, 2017-04-29
|
||||
* **Use HKDF to derive separate keys for GCM and EME**
|
||||
* New feature flag: `HKDF` (enabled by default)
|
||||
* This is a forwards-compatible change. gocryptfs v1.3 can mount
|
||||
filesystems created by earlier versions but not the other way round.
|
||||
* **Enable Raw64 filename encoding by default (gets rid of trailing `==` characters)**
|
||||
* This is a forwards-compatible change. gocryptfs v1.3 can mount
|
||||
filesystems created by earlier versions but not the other way round.
|
||||
* Drop Go 1.4 compatibility. You now need Go 1.5 (released 2015-08-19)
|
||||
or higher to build gocryptfs.
|
||||
* Add `-serialize_reads` command-line option
|
||||
* This can greatly improve performance on storage
|
||||
that is very slow for concurrent out-of-order reads. Example:
|
||||
Amazon Cloud Drive ([#92](https://github.com/rfjakob/gocryptfs/issues/92))
|
||||
* Reject file-header-only files
|
||||
([#90 2.2](https://github.com/rfjakob/gocryptfs/issues/90),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/14038a1644f17f50b113a05d09a2a0a3b3e973b2))
|
||||
* Increase max password size to 2048 bytes ([#93](https://github.com/rfjakob/gocryptfs/issues/93))
|
||||
* Use stable 64-bit inode numbers in reverse mode
|
||||
* This may cause problems for very old 32-bit applications
|
||||
that were compiled without Large File Support.
|
||||
* Passing "--" now also blocks "-o" parsing
|
||||
|
||||
v1.2.1, 2017-02-26
|
||||
* Add an integrated speed test, `gocryptfs -speed`
|
||||
* Limit password size to 1000 bytes and reject trailing garbage after the newline
|
||||
* Make the test suite work on [Mac OS X](https://github.com/rfjakob/gocryptfs/issues/15)
|
||||
* Handle additional corner cases in `-ctlsock` path sanitization
|
||||
* Use dedicated exit code 12 on "password incorrect"
|
||||
|
||||
v1.2, 2016-12-04
|
||||
* Add a control socket interface. Allows to encrypt and decrypt filenames.
|
||||
For details see [backintime#644](https://github.com/bit-team/backintime/issues/644#issuecomment-259835183).
|
||||
* New command-line option: `-ctlsock`
|
||||
* Under certain circumstances, concurrent truncate and read could return
|
||||
an I/O error. This is fixed by introducing a global open file table
|
||||
that stores the file IDs
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/0489d08ae21107990d0efd0685443293aa26b35f)).
|
||||
* Coalesce 4kB ciphertext block writes up to the size requested through
|
||||
the write FUSE call
|
||||
([commit with benchmarks](https://github.com/rfjakob/gocryptfs/commit/024511d9c71558be4b1169d6bb43bd18d65539e0))
|
||||
* Add `-noprealloc` command-line option
|
||||
* Greatly speeds up writes on Btrfs
|
||||
([#63](https://github.com/rfjakob/gocryptfs/issues/63))
|
||||
at the cost of reduced out-of-space robustness.
|
||||
* This is a workaround for Btrfs' slow fallocate(2)
|
||||
* Preserve owner for symlinks an device files (fixes bug [#64](https://github.com/rfjakob/gocryptfs/issues/64))
|
||||
* Include rendered man page `gocryptfs.1` in the release tarball
|
||||
|
||||
v1.1.1, 2016-10-30
|
||||
* Fix a panic on setting file timestamps ([go-fuse#131](https://github.com/hanwen/go-fuse/pull/131))
|
||||
* Work around an issue in tmpfs that caused a panic in xfstests generic/075
|
||||
([gocryptfs#56](https://github.com/rfjakob/gocryptfs/issues/56))
|
||||
* Optimize NFS streaming writes
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/a08d55f42d5b11e265a8617bee16babceebfd026))
|
||||
|
||||
v1.1, 2016-10-19
|
||||
* **Add reverse mode ([#19](https://github.com/rfjakob/gocryptfs/issues/19))**
|
||||
* AES-SIV (RFC5297) encryption to implement deterministic encryption
|
||||
securely. Uses the excellent
|
||||
[jacobsa/crypto](https://github.com/jacobsa/crypto) library.
|
||||
The corresponding feature flag is called `AESSIV`.
|
||||
* New command-line options: `-reverse`, `-aessiv`
|
||||
* Filesystems using reverse mode can only be mounted with gocryptfs v1.1
|
||||
and later.
|
||||
* The default, forward mode, stays fully compatible with older versions.
|
||||
Forward mode will keep using GCM because it is much faster.
|
||||
* Accept `-o foo,bar,baz`-style options that are passed at the end of
|
||||
the command-line, like mount(1) does. All other options must still
|
||||
precede the passed paths.
|
||||
* This allows **mounting from /etc/fstab**. See
|
||||
[#45](https://github.com/rfjakob/gocryptfs/issues/45) for details.
|
||||
* **Mounting on login using pam_mount** works as well. It is
|
||||
[described in the wiki](https://github.com/rfjakob/gocryptfs/wiki/Mounting-on-login-using-pam_mount).
|
||||
* To prevent confusion, the old `-o` option had to be renamed. It is now
|
||||
called `-ko`. Arguments to `-ko` are passed directly to the kernel.
|
||||
* New `-passfile` command-line option. Provides an easier way to read
|
||||
the password from a file. Internally, this is equivalent to
|
||||
`-extpass "/bin/cat FILE"`.
|
||||
* Enable changing the password when you only know the master key
|
||||
([#28](https://github.com/rfjakob/gocryptfs/issues/28))
|
||||
|
||||
v1.0, 2016-07-17
|
||||
* Deprecate very old filesystems, stage 3/3
|
||||
* Filesystems created by v0.6 can no longer be mounted
|
||||
* Drop command-line options `-gcmiv128`, `-emenames`, `-diriv`. These
|
||||
are now always enabled.
|
||||
* Add fallocate(2) support
|
||||
* New command-line option `-o`
|
||||
* Allows to pass mount options directly to the kernel
|
||||
* Add support for device files and suid binaries
|
||||
* Only works when running as root
|
||||
* Must be explicitly enabled by passing "-o dev" or "-o suid" or "-o suid,dev"
|
||||
* Experimental Mac OS X support. See
|
||||
[ticket #15](https://github.com/rfjakob/gocryptfs/issues/15) for details.
|
||||
|
||||
v0.12, 2016-06-19
|
||||
* Deprecate very old filesystems, stage 2/3
|
||||
* Filesystems created by v0.6 and older can only be mounted read-only
|
||||
* A [message](https://github.com/rfjakob/gocryptfs/blob/v0.12/internal/configfile/config_file.go#L120)
|
||||
explaining the situation is printed as well
|
||||
* New command line option: `-ro`
|
||||
* Mounts the filesystem read-only
|
||||
* Accept password from stdin as well ([ticket #30](https://github.com/rfjakob/gocryptfs/issues/30))
|
||||
|
||||
v0.11, 2016-06-10
|
||||
* Deprecate very old filesystems, stage 1/3
|
||||
* Filesystems created by v0.6 and older can still be mounted but a
|
||||
[warning](https://github.com/rfjakob/gocryptfs/blob/v0.11/internal/configfile/config_file.go#L120)
|
||||
is printed
|
||||
* See [ticket #29](https://github.com/rfjakob/gocryptfs/issues/29) for details and
|
||||
join the discussion
|
||||
* Add rsync stress test "pingpong-rsync.bash"
|
||||
* Fix chown and utimens failures that caused rsync to complain
|
||||
* Build release binaries with Go 1.6.2
|
||||
* Big speedup for CPUs with AES-NI, see [ticket #23](https://github.com/rfjakob/gocryptfs/issues/23)
|
||||
|
||||
v0.10, 2016-05-30
|
||||
* **Replace `spacemonkeygo/openssl` with `stupidgcm`**
|
||||
* gocryptfs now has its own thin wrapper to OpenSSL's GCM implementation
|
||||
called `stupidgcm`.
|
||||
* This should fix the [compile issues](https://github.com/rfjakob/gocryptfs/issues/21)
|
||||
people are seeing with `spacemonkeygo/openssl`. It also gets us
|
||||
a 20% performance boost for streaming writes.
|
||||
* **Automatically choose between OpenSSL and Go crypto** [issue #23](https://github.com/rfjakob/gocryptfs/issues/23)
|
||||
* Go 1.6 added an optimized GCM implementation in amd64 assembly that uses AES-NI.
|
||||
This is faster than OpenSSL and is used if available. In all other
|
||||
cases OpenSSL is much faster and is used instead.
|
||||
* `-openssl=auto` is the new default
|
||||
* Passing `-openssl=true/false` overrides the autodetection.
|
||||
* Warn but continue anyway if fallocate(2) is not supported by the
|
||||
underlying filesystem, see [issue #22](https://github.com/rfjakob/gocryptfs/issues/22)
|
||||
* Enables to use gocryptfs on ZFS and ext3, albeit with reduced out-of-space safety.
|
||||
* [Fix statfs](https://github.com/rfjakob/gocryptfs/pull/27), by @lxp
|
||||
* Fix a fsstress [failure](https://github.com/hanwen/go-fuse/issues/106)
|
||||
in the go-fuse library.
|
||||
|
||||
v0.9, 2016-04-10
|
||||
* **Long file name support**
|
||||
* gocryptfs now supports file names up to 255 characters.
|
||||
* This is a forwards-compatible change. gocryptfs v0.9 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* Refactor gocryptfs into multiple "internal" packages
|
||||
* New command-line options:
|
||||
* `-longnames`: Enable long file name support (default true)
|
||||
* `-nosyslog`: Print messages to stdout and stderr instead of syslog (default false)
|
||||
* `-wpanic`: Make warning messages fatal (used for testing)
|
||||
* `-d`: Alias for `-debug`
|
||||
* `-q`: Alias for `-quiet`
|
||||
|
||||
v0.8, 2016-01-23
|
||||
* Redirect output to syslog when running in the background
|
||||
* New command-line option:
|
||||
* `-memprofile`: Write a memory allocation debugging profile the specified
|
||||
file
|
||||
|
||||
v0.7.2, 2016-01-19
|
||||
* **Fix performance issue in small file creation**
|
||||
* This brings performance on-par with EncFS paranoia mode, with streaming writes
|
||||
significantly faster
|
||||
* The actual [fix](https://github.com/hanwen/go-fuse/commit/c4b6b7949716d13eec856baffc7b7941ae21778c)
|
||||
is in the go-fuse library. There are no code changes in gocryptfs.
|
||||
|
||||
v0.7.1, 2016-01-09
|
||||
* Make the `build.bash` script compatible with Go 1.3
|
||||
* Disable fallocate on OSX (system call not available)
|
||||
* Introduce pre-built binaries for Fedora 23 and Debian 8
|
||||
|
||||
v0.7, 2015-12-20
|
||||
* **Extend GCM IV size to 128 bit from Go's default of 96 bit**
|
||||
* This pushes back the birthday bound to make IV collisions virtually
|
||||
impossible
|
||||
* This is a forwards-compatible change. gocryptfs v0.7 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* New command-line option:
|
||||
* `-gcmiv128`: Use 128-bit GCM IVs (default true)
|
||||
|
||||
v0.6, 2015-12-08
|
||||
* **Wide-block filename encryption using EME + DirIV**
|
||||
* EME (ECB-Mix-ECB) provides even better security than CBC as it fixes
|
||||
the prefix leak. The used Go EME implementation is
|
||||
https://github.com/rfjakob/eme which is, as far as I know, the first
|
||||
implementation of EME in Go.
|
||||
* This is a forwards-compatible change. gocryptfs v0.6 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* New command-line option:
|
||||
* `-emenames`: Enable EME filename encryption (default true)
|
||||
|
||||
v0.5.1, 2015-12-06
|
||||
* Fix a rename regression caused by DirIV and add test case
|
||||
* Use fallocate to guard against out-of-space errors
|
||||
|
||||
v0.5, 2015-12-04
|
||||
* **Stronger filename encryption: DirIV**
|
||||
* Each directory gets a random 128 bit file name IV on creation,
|
||||
stored in `gocryptfs.diriv`
|
||||
* This makes it impossible to identify identically-named files across
|
||||
directories
|
||||
* A single-entry IV cache brings the performance cost of DirIV close to
|
||||
zero for common operations (see performance.txt)
|
||||
* This is a forwards-compatible change. gocryptfs v0.5 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* New command-line option:
|
||||
* `-diriv`: Use the new per-directory IV file name encryption (default true)
|
||||
* `-scryptn`: allows to set the scrypt cost parameter N. This option
|
||||
can be used for faster mounting at the cost of lower brute-force
|
||||
resistance. It was mainly added to speed up the automated tests.
|
||||
|
||||
v0.4, 2015-11-15
|
||||
* New command-line options:
|
||||
* `-plaintextnames`: disables filename encryption, added on user request
|
||||
* `-extpass`: calls an external program for prompting for the password
|
||||
* `-config`: allows to specify a custom gocryptfs.conf path
|
||||
* Add `FeatureFlags` gocryptfs.conf parameter
|
||||
* This is a config format change, hence the on-disk format is incremented
|
||||
* Used for ext4-style filesystem feature flags. This should help avoid future
|
||||
format changes. The first user is `-plaintextnames`.
|
||||
* On-disk format 2
|
||||
|
||||
v0.3, 2015-11-01
|
||||
* **Add a random 128 bit file header to authenticate file->block ownership**
|
||||
* This is an on-disk-format change
|
||||
* On-disk format 1
|
||||
|
||||
v0.2, 2015-10-11
|
||||
* Replace bash daemonization wrapper with native Go implementation
|
||||
* Better user feedback on mount failures
|
||||
|
||||
v0.1, 2015-10-07
|
||||
* First release
|
||||
* On-disk format 0
|
||||
## Warning !
|
||||
The only goal of this library is to be integrated in [DroidFS](https://forge.chapril.org/hardcoresushi/DroidFS). It's not actually ready for other usages. libgocryptfs doesn't implement all features provided by gocryptfs like symbolic links creation, thread-safety, reverse volume creation... Use it at your own risk !
|
13
allocator/allocator32.go
Normal file
13
allocator/allocator32.go
Normal file
@ -0,0 +1,13 @@
|
||||
// +build !arm64
|
||||
// +build !amd64
|
||||
|
||||
package allocator
|
||||
|
||||
import (
|
||||
"C"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Malloc(size int) unsafe.Pointer {
|
||||
return C.malloc(C.uint(C.sizeof_int * size))
|
||||
}
|
12
allocator/allocator64.go
Normal file
12
allocator/allocator64.go
Normal file
@ -0,0 +1,12 @@
|
||||
// +build arm64 amd64
|
||||
|
||||
package allocator
|
||||
|
||||
import (
|
||||
"C"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Malloc(size int) unsafe.Pointer {
|
||||
return C.malloc(C.ulong(C.sizeof_int * size))
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Benchmark gocryptfs' reverse mode
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
MYNAME=$(basename "$0")
|
||||
source tests/fuse-unmount.bash
|
||||
|
||||
# Download /tmp/linux-3.0.tar.gz
|
||||
./tests/dl-linux-tarball.bash
|
||||
|
||||
cd /tmp
|
||||
PLAIN=linux-3.0
|
||||
|
||||
SIZE=0
|
||||
if [[ -d $PLAIN ]]; then
|
||||
SIZE=$(du -s --apparent-size $PLAIN | cut -f1)
|
||||
fi
|
||||
|
||||
|
||||
if [[ $SIZE -ne 412334 ]] ; then
|
||||
echo "Extracting linux-3.0.tar.gz"
|
||||
rm -Rf $PLAIN
|
||||
tar xf linux-3.0.tar.gz
|
||||
fi
|
||||
|
||||
rm -f $PLAIN/.gocryptfs.reverse.conf
|
||||
gocryptfs -q -init -reverse -extpass="echo test" -scryptn=10 $PLAIN
|
||||
|
||||
MNT=$(mktemp -d /tmp/linux-3.0.reverse.mnt.XXX)
|
||||
|
||||
# Cleanup trap
|
||||
trap 'rm -f "$PLAIN/.gocryptfs.reverse.conf" ; fuse-unmount -z "$MNT" ; rmdir "$MNT"' EXIT
|
||||
|
||||
# Mount
|
||||
gocryptfs -q -reverse -extpass="echo test" "$PLAIN" "$MNT"
|
||||
|
||||
# Execute command, discard all stdout output, print elapsed time
|
||||
# (to stderr, unfortunately).
|
||||
function etime {
|
||||
# Make the bash builtin "time" print out only the elapse wall clock
|
||||
# seconds
|
||||
TIMEFORMAT=%R
|
||||
time "$@" > /dev/null
|
||||
}
|
||||
|
||||
echo -n "LS: "
|
||||
etime ls -lR "$MNT"
|
||||
echo -n "CAT: "
|
||||
etime find "$MNT" -type f -exec cat {} +
|
103
benchmark.bash
103
benchmark.bash
@ -1,103 +0,0 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Run the set of "canonical" benchmarks that are shown on
|
||||
# https://nuetzlich.net/gocryptfs/comparison/
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
MYNAME=$(basename "$0")
|
||||
source tests/fuse-unmount.bash
|
||||
|
||||
function usage {
|
||||
echo "Usage: $MYNAME [-encfs] [-openssl=true] [-openssl=false] [-dd] [DIR]"
|
||||
}
|
||||
|
||||
OPT_ENCFS=0
|
||||
OPT_LOOPBACK=0
|
||||
OPT_OPENSSL=""
|
||||
OPT_DIR=""
|
||||
DD_ONLY=""
|
||||
|
||||
while [[ $# -gt 0 ]] ; do
|
||||
case $1 in
|
||||
-h)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
-encfs)
|
||||
OPT_ENCFS=1
|
||||
;;
|
||||
-openssl=true)
|
||||
OPT_OPENSSL="-openssl=true"
|
||||
;;
|
||||
-openssl=false)
|
||||
OPT_OPENSSL="-openssl=false"
|
||||
;;
|
||||
-dd)
|
||||
DD_ONLY=1
|
||||
;;
|
||||
-loopback)
|
||||
OPT_LOOPBACK=1
|
||||
;;
|
||||
-*)
|
||||
echo "Invalid option: $1"
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
*)
|
||||
if [[ -n $OPT_DIR ]] ; then
|
||||
echo "Duplicate DIR argument: $1"
|
||||
usage
|
||||
exit 3
|
||||
fi
|
||||
OPT_DIR=$1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -z $OPT_DIR ]] ; then
|
||||
OPT_DIR=/tmp
|
||||
fi
|
||||
|
||||
# Create directories
|
||||
CRYPT=$(mktemp -d "$OPT_DIR/$MYNAME.XXX")
|
||||
MNT=$CRYPT.mnt
|
||||
mkdir "$MNT"
|
||||
|
||||
# Mount
|
||||
if [[ $OPT_ENCFS -eq 1 ]]; then
|
||||
if [[ -n $OPT_OPENSSL ]] ; then
|
||||
echo "The option $OPT_OPENSSL only works with gocryptfs"
|
||||
exit 1
|
||||
fi
|
||||
echo -n "Testing EncFS at $CRYPT: "
|
||||
encfs --version
|
||||
encfs --extpass="echo test" --standard "$CRYPT" "$MNT" > /dev/null
|
||||
elif [[ $OPT_LOOPBACK -eq 1 ]]; then
|
||||
echo "Testing go-fuse loopback"
|
||||
"$HOME/go/src/github.com/hanwen/go-fuse/example/loopback/loopback" "$MNT" "$CRYPT" &
|
||||
sleep 0.5
|
||||
else
|
||||
echo -n "Testing gocryptfs at $CRYPT: "
|
||||
gocryptfs -version
|
||||
gocryptfs -q -init -extpass="echo test" -scryptn=10 "$CRYPT"
|
||||
gocryptfs -q -extpass="echo test" $OPT_OPENSSL "$CRYPT" "$MNT"
|
||||
fi
|
||||
|
||||
# Make sure we have actually mounted something
|
||||
if ! mountpoint "$MNT" ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup trap
|
||||
trap 'cd /; fuse-unmount -z "$MNT"; rm -rf "$CRYPT" "$MNT"' EXIT
|
||||
|
||||
# Benchmarks
|
||||
if [[ $DD_ONLY -eq 1 ]]; then
|
||||
echo -n "WRITE: "
|
||||
dd if=/dev/zero "of=$MNT/zero" bs=131072 count=20000 2>&1 | tail -n 1
|
||||
rm "$MNT/zero"
|
||||
else
|
||||
./tests/canonical-benchmarks.bash "$MNT"
|
||||
fi
|
||||
|
@ -1,10 +0,0 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
CGO_ENABLED=0 source ./build.bash -tags without_openssl
|
||||
|
||||
if ldd gocryptfs 2> /dev/null ; then
|
||||
echo "build-without-openssl.bash: error: compiled binary is not static"
|
||||
exit 1
|
||||
fi
|
104
build.bash
104
build.bash
@ -1,104 +0,0 @@
|
||||
#!/bin/bash -eu
|
||||
#
|
||||
# Compile gocryptfs and bake the git version string of itself and the go-fuse
|
||||
# library into the binary.
|
||||
#
|
||||
# If you want to fake a build date to reproduce a specific build,
|
||||
# you can use:
|
||||
# BUILDDATE=2017-02-03 ./build.bash
|
||||
# or
|
||||
# SOURCE_DATE_EPOCH=1544192417 ./build.bash
|
||||
# .
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Make sure we have the go binary
|
||||
go version > /dev/null
|
||||
|
||||
# Enable Go Modules on Go 1.11 and 1.12
|
||||
# https://dev.to/maelvls/why-is-go111module-everywhere-and-everything-about-go-modules-24k#-raw-go111module-endraw-with-go-111-and-112
|
||||
export GO111MODULE=on
|
||||
|
||||
# GOPATH may contain multiple paths separated by ":"
|
||||
GOPATH1=$(go env GOPATH | cut -f1 -d:)
|
||||
|
||||
# gocryptfs version according to git or a VERSION file
|
||||
if [[ -d .git ]] ; then
|
||||
GITVERSION=$(git describe --tags --dirty || echo "[no_tags_found]")
|
||||
GITBRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [[ -n $GITBRANCH && $GITBRANCH != master ]] ; then
|
||||
GITVERSION="$GITVERSION.$GITBRANCH"
|
||||
fi
|
||||
elif [[ -f VERSION ]] ; then
|
||||
GITVERSION=$(cat VERSION)
|
||||
else
|
||||
echo "Warning: could not determine gocryptfs version"
|
||||
GITVERSION="[unknown]"
|
||||
fi
|
||||
|
||||
# go-fuse version, if available
|
||||
if [[ -d vendor/github.com/hanwen/go-fuse ]] ; then
|
||||
GITVERSIONFUSE="[vendored]"
|
||||
else
|
||||
# go-fuse version according to Go Modules
|
||||
FAIL=0
|
||||
OUT=$(go list -m github.com/hanwen/go-fuse/v2 | cut -d' ' -f2-) || FAIL=1
|
||||
if [[ $FAIL -eq 0 ]]; then
|
||||
GITVERSIONFUSE=$OUT
|
||||
else
|
||||
echo "Warning: could not determine go-fuse version"
|
||||
GITVERSIONFUSE="[unknown]"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build date, something like "2017-09-06". Don't override BUILDDATE
|
||||
# if it is already set. This may be done for reproducible builds.
|
||||
if [[ -z ${BUILDDATE:-} ]] ; then
|
||||
BUILDDATE=$(date +%Y-%m-%d)
|
||||
fi
|
||||
|
||||
# If SOURCE_DATE_EPOCH is set, it overrides BUILDDATE. This is the
|
||||
# standard environment variable for faking the date in reproducible builds.
|
||||
if [[ -n ${SOURCE_DATE_EPOCH:-} ]] ; then
|
||||
BUILDDATE=$(date --utc --date="@${SOURCE_DATE_EPOCH}" +%Y-%m-%d)
|
||||
fi
|
||||
|
||||
# Only set GOFLAGS if it is not already set by the user
|
||||
if [[ -z ${GOFLAGS:-} ]] ; then
|
||||
GOFLAGS=""
|
||||
# For reproducible builds, we get rid of $HOME references in the
|
||||
# binary using "-trimpath".
|
||||
# However, -trimpath needs Go 1.13+, and we support Go 1.11 and Go 1.12
|
||||
# too. So don't add it there.
|
||||
GV=$(go version)
|
||||
if [[ $GV != *"1.11"* && $GV != *"1.12"* ]] ; then
|
||||
GOFLAGS="-trimpath"
|
||||
fi
|
||||
# Also, Fedora and Arch want pie enabled, so enable it.
|
||||
# * https://fedoraproject.org/wiki/Changes/golang-buildmode-pie
|
||||
# * https://github.com/rfjakob/gocryptfs/pull/460
|
||||
# But not with CGO_ENABLED=0 (https://github.com/golang/go/issues/30986)!
|
||||
if [[ ${CGO_ENABLED:-1} -ne 0 ]] ; then
|
||||
GOFLAGS="$GOFLAGS -buildmode=pie"
|
||||
fi
|
||||
export GOFLAGS
|
||||
fi
|
||||
|
||||
GO_LDFLAGS="-X \"main.GitVersion=$GITVERSION\" -X \"main.GitVersionFuse=$GITVERSIONFUSE\" -X \"main.BuildDate=$BUILDDATE\""
|
||||
|
||||
# If LDFLAGS is set, add it as "-extldflags".
|
||||
if [[ -n ${LDFLAGS:-} ]] ; then
|
||||
GO_LDFLAGS="$GO_LDFLAGS \"-extldflags=$LDFLAGS\""
|
||||
fi
|
||||
|
||||
# Actual "go build" call for gocryptfs
|
||||
go build "-ldflags=$GO_LDFLAGS" "$@"
|
||||
# Additional binaries
|
||||
for d in gocryptfs-xray contrib/statfs contrib/findholes contrib/atomicrename ; do
|
||||
(cd "$d"; go build "-ldflags=$GO_LDFLAGS" "$@")
|
||||
done
|
||||
|
||||
./gocryptfs -version
|
||||
|
||||
mkdir -p "$GOPATH1/bin"
|
||||
cp -af gocryptfs "$GOPATH1/bin"
|
72
build.sh
Executable file
72
build.sh
Executable file
@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z ${ANDROID_NDK_HOME+x} ]; then
|
||||
echo "Error: \$ANDROID_NDK_HOME is not defined."
|
||||
elif [ -z ${OPENSSL_PATH+x} ]; then
|
||||
echo "Error: \$OPENSSL_PATH is not defined."
|
||||
else
|
||||
NDK_BIN_PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
declare -a ABIs=("x86_64" "x86" "arm64-v8a" "armeabi-v7a")
|
||||
|
||||
compile_openssl(){
|
||||
if [ ! -d "./lib/$1" ]; then
|
||||
if [ "$1" = "x86_64" ]; then
|
||||
OPENSSL_ARCH="android-x86_64"
|
||||
elif [ "$1" = "x86" ]; then
|
||||
OPENSSL_ARCH="android-x86"
|
||||
elif [ "$1" = "arm64-v8a" ]; then
|
||||
OPENSSL_ARCH="android-arm64"
|
||||
elif [ "$1" = "armeabi-v7a" ]; then
|
||||
OPENSSL_ARCH="android-arm"
|
||||
else
|
||||
echo "Invalid ABI: $1"
|
||||
exit
|
||||
fi
|
||||
|
||||
export CFLAGS=-D__ANDROID_API__=21
|
||||
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$PATH
|
||||
(cd "$OPENSSL_PATH" && if [ -f "Makefile" ]; then make clean; fi && ./Configure $OPENSSL_ARCH -D__ANDROID_API__=21 && make -j4 build_libs)
|
||||
mkdir -p "./lib/$1" && cp "$OPENSSL_PATH/libcrypto.a" "$OPENSSL_PATH/libssl.a" "./lib/$1"
|
||||
mkdir -p "./include/$1" && cp -r "$OPENSSL_PATH"/include/* "./include/$1/"
|
||||
fi
|
||||
}
|
||||
|
||||
compile_for_arch() {
|
||||
compile_openssl $1
|
||||
if [ "$1" = "x86_64" ]; then
|
||||
CFN="x86_64-linux-android21-clang"
|
||||
elif [ "$1" = "x86" ]; then
|
||||
export GOARCH=386
|
||||
CFN="i686-linux-android21-clang"
|
||||
elif [ "$1" = "arm64-v8a" ]; then
|
||||
CFN="aarch64-linux-android21-clang"
|
||||
export GOARCH=arm64
|
||||
export GOARM=7
|
||||
elif [ "$1" = "armeabi-v7a" ]; then
|
||||
CFN="armv7a-linux-androideabi21-clang"
|
||||
export GOARCH=arm
|
||||
export GOARM=7
|
||||
else
|
||||
echo "Invalid ABI: $1"
|
||||
exit
|
||||
fi
|
||||
|
||||
export CC="$NDK_BIN_PATH/$CFN"
|
||||
export CXX="$NDK_BIN_PATH/$CFN++"
|
||||
export CGO_ENABLED=1
|
||||
export GOOS=android
|
||||
export CGO_CFLAGS="-I ${PWD}/include/$1"
|
||||
export CGO_LDFLAGS="-Wl,-soname=libgocryptfs.so -L${PWD}/lib/$1"
|
||||
go build -o build/$1/libgocryptfs.so -buildmode=c-shared
|
||||
}
|
||||
|
||||
if [ "$#" -eq 1 ]; then
|
||||
compile_for_arch $1
|
||||
else
|
||||
for abi in ${ABIs[@]}; do
|
||||
echo "Compiling for $abi..."
|
||||
compile_for_arch $abi
|
||||
done
|
||||
fi
|
||||
echo "Done."
|
||||
fi
|
332
cli_args.go
332
cli_args.go
@ -1,332 +0,0 @@
|
||||
package main
|
||||
|
||||
// Should be initialized before anything else.
|
||||
// This import line MUST be in the alphabitcally first source code file of
|
||||
// package main!
|
||||
import _ "github.com/rfjakob/gocryptfs/internal/ensurefds012"
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
// argContainer stores the parsed CLI options and arguments
|
||||
type argContainer struct {
|
||||
debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
|
||||
plaintextnames, quiet, nosyslog, wpanic,
|
||||
longnames, allow_other, reverse, aessiv, nonempty, raw64,
|
||||
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
|
||||
sharedstorage, devrandom, fsck bool
|
||||
// Mount options with opposites
|
||||
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
|
||||
masterkey, mountpoint, cipherdir, cpuprofile,
|
||||
memprofile, ko, ctlsock, fsname, force_owner, trace, fido2 string
|
||||
// -extpass, -badname, -passfile can be passed multiple times
|
||||
extpass, badname, passfile multipleStrings
|
||||
// For reverse mode, several ways to specify exclusions. All can be specified multiple times.
|
||||
exclude, excludeWildcard, excludeFrom multipleStrings
|
||||
// Configuration file name override
|
||||
config string
|
||||
notifypid, scryptn int
|
||||
// Idle time before autounmount
|
||||
idle time.Duration
|
||||
// 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
|
||||
// _ctlsockFd stores the control socket file descriptor (ctlsock stores the path)
|
||||
_ctlsockFd net.Listener
|
||||
// _forceOwner is, if non-nil, a parsed, validated Owner (as opposed to the string above)
|
||||
_forceOwner *fuse.Owner
|
||||
// _explicitScryptn is true then the user passed "-scryptn=xyz"
|
||||
_explicitScryptn bool
|
||||
}
|
||||
|
||||
type multipleStrings []string
|
||||
|
||||
func (s *multipleStrings) String() string {
|
||||
s2 := []string(*s)
|
||||
return fmt.Sprint(s2)
|
||||
}
|
||||
|
||||
func (s *multipleStrings) Set(val string) error {
|
||||
*s = append(*s, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *multipleStrings) Empty() bool {
|
||||
s2 := []string(*s)
|
||||
return len(s2) == 0
|
||||
}
|
||||
|
||||
var flagSet *flag.FlagSet
|
||||
|
||||
// prefixOArgs transform options passed via "-o foo,bar" into regular options
|
||||
// like "-foo -bar" and prefixes them to the command line.
|
||||
// Testcases in TestPrefixOArgs().
|
||||
func prefixOArgs(osArgs []string) ([]string, error) {
|
||||
// Need at least 3, example: gocryptfs -o foo,bar
|
||||
// ^ 0 ^ 1 ^ 2
|
||||
if len(osArgs) < 3 {
|
||||
return osArgs, nil
|
||||
}
|
||||
// Passing "--" disables "-o" parsing. Ignore element 0 (program name).
|
||||
for _, v := range osArgs[1:] {
|
||||
if v == "--" {
|
||||
return osArgs, nil
|
||||
}
|
||||
}
|
||||
// Find and extract "-o foo,bar"
|
||||
var otherArgs, oOpts []string
|
||||
for i := 1; i < len(osArgs); i++ {
|
||||
if osArgs[i] == "-o" {
|
||||
// Last argument?
|
||||
if i+1 >= len(osArgs) {
|
||||
return nil, fmt.Errorf("The \"-o\" option requires an argument")
|
||||
}
|
||||
oOpts = strings.Split(osArgs[i+1], ",")
|
||||
// Skip over the arguments to "-o"
|
||||
i++
|
||||
} else if strings.HasPrefix(osArgs[i], "-o=") {
|
||||
oOpts = strings.Split(osArgs[i][3:], ",")
|
||||
} else {
|
||||
otherArgs = append(otherArgs, osArgs[i])
|
||||
}
|
||||
}
|
||||
// Start with program name
|
||||
newArgs := []string{osArgs[0]}
|
||||
// Add options from "-o"
|
||||
for _, o := range oOpts {
|
||||
if o == "" {
|
||||
continue
|
||||
}
|
||||
if o == "o" || o == "-o" {
|
||||
tlog.Fatal.Printf("You can't pass \"-o\" to \"-o\"")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
newArgs = append(newArgs, "-"+o)
|
||||
}
|
||||
// Add other arguments
|
||||
newArgs = append(newArgs, otherArgs...)
|
||||
return newArgs, nil
|
||||
}
|
||||
|
||||
// parseCliOpts - parse command line options (i.e. arguments that start with "-")
|
||||
func parseCliOpts() (args argContainer) {
|
||||
var err error
|
||||
var opensslAuto string
|
||||
|
||||
os.Args, err = prefixOArgs(os.Args)
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
|
||||
flagSet = flag.NewFlagSet(tlog.ProgramName, flag.ContinueOnError)
|
||||
flagSet.Usage = func() {}
|
||||
flagSet.BoolVar(&args.debug, "d", false, "")
|
||||
flagSet.BoolVar(&args.debug, "debug", false, "Enable debug output")
|
||||
flagSet.BoolVar(&args.fusedebug, "fusedebug", false, "Enable fuse library debug output")
|
||||
flagSet.BoolVar(&args.init, "init", false, "Initialize encrypted directory")
|
||||
flagSet.BoolVar(&args.zerokey, "zerokey", false, "Use all-zero dummy master key")
|
||||
// Tri-state true/false/auto
|
||||
flagSet.StringVar(&opensslAuto, "openssl", "auto", "Use OpenSSL instead of built-in Go crypto")
|
||||
flagSet.BoolVar(&args.passwd, "passwd", false, "Change password")
|
||||
flagSet.BoolVar(&args.fg, "f", false, "")
|
||||
flagSet.BoolVar(&args.fg, "fg", false, "Stay in the foreground")
|
||||
flagSet.BoolVar(&args.version, "version", false, "Print version and exit")
|
||||
flagSet.BoolVar(&args.plaintextnames, "plaintextnames", false, "Do not encrypt file names")
|
||||
flagSet.BoolVar(&args.quiet, "q", false, "")
|
||||
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.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")
|
||||
flagSet.BoolVar(&args.aessiv, "aessiv", false, "AES-SIV encryption")
|
||||
flagSet.BoolVar(&args.nonempty, "nonempty", false, "Allow mounting over non-empty directories")
|
||||
flagSet.BoolVar(&args.raw64, "raw64", true, "Use unpadded base64 for file names")
|
||||
flagSet.BoolVar(&args.noprealloc, "noprealloc", false, "Disable preallocation before writing")
|
||||
flagSet.BoolVar(&args.speed, "speed", false, "Run crypto speed test")
|
||||
flagSet.BoolVar(&args.hkdf, "hkdf", true, "Use HKDF as an additional key derivation step")
|
||||
flagSet.BoolVar(&args.serialize_reads, "serialize_reads", false, "Try to serialize read operations")
|
||||
flagSet.BoolVar(&args.forcedecode, "forcedecode", false, "Force decode of files even if integrity check fails."+
|
||||
" Requires gocryptfs to be compiled with openssl support and implies -openssl true")
|
||||
flagSet.BoolVar(&args.hh, "hh", false, "Show this long help text")
|
||||
flagSet.BoolVar(&args.info, "info", false, "Display information about CIPHERDIR")
|
||||
flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer")
|
||||
flagSet.BoolVar(&args.devrandom, "devrandom", false, "Use /dev/random for generating master key")
|
||||
flagSet.BoolVar(&args.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR")
|
||||
|
||||
// Mount options with opposites
|
||||
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")
|
||||
flagSet.BoolVar(&args.nodev, "nodev", false, "Deny device files")
|
||||
flagSet.BoolVar(&args.suid, "suid", false, "Allow suid binaries")
|
||||
flagSet.BoolVar(&args.nosuid, "nosuid", false, "Deny suid binaries")
|
||||
flagSet.BoolVar(&args.exec, "exec", false, "Allow executables")
|
||||
flagSet.BoolVar(&args.noexec, "noexec", false, "Deny executables")
|
||||
flagSet.BoolVar(&args.rw, "rw", false, "Mount the filesystem read-write")
|
||||
flagSet.BoolVar(&args.ro, "ro", false, "Mount the filesystem read-only")
|
||||
flagSet.BoolVar(&args.kernel_cache, "kernel_cache", false, "Enable the FUSE kernel_cache option")
|
||||
flagSet.BoolVar(&args.acl, "acl", false, "Enforce ACLs")
|
||||
|
||||
flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key")
|
||||
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")
|
||||
flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file")
|
||||
flagSet.StringVar(&args.config, "config", "", "Use specified config file instead of CIPHERDIR/gocryptfs.conf")
|
||||
flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list")
|
||||
flagSet.StringVar(&args.ctlsock, "ctlsock", "", "Create control socket at specified path")
|
||||
flagSet.StringVar(&args.fsname, "fsname", "", "Override the filesystem name")
|
||||
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
|
||||
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")
|
||||
flagSet.StringVar(&args.fido2, "fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
|
||||
|
||||
// Exclusion options
|
||||
flagSet.Var(&args.exclude, "e", "Alias for -exclude")
|
||||
flagSet.Var(&args.exclude, "exclude", "Exclude relative path from reverse view")
|
||||
flagSet.Var(&args.excludeWildcard, "ew", "Alias for -exclude-wildcard")
|
||||
flagSet.Var(&args.excludeWildcard, "exclude-wildcard", "Exclude path from reverse view, supporting wildcards")
|
||||
flagSet.Var(&args.excludeFrom, "exclude-from", "File from which to read exclusion patterns (with -exclude-wildcard syntax)")
|
||||
|
||||
// multipleStrings options ([]string)
|
||||
flagSet.Var(&args.extpass, "extpass", "Use external program for the password prompt")
|
||||
flagSet.Var(&args.badname, "badname", "Glob pattern invalid file names that should be shown")
|
||||
flagSet.Var(&args.passfile, "passfile", "Read password from file")
|
||||
|
||||
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
|
||||
"successful mount - used internally for daemonization")
|
||||
const scryptn = "scryptn"
|
||||
flagSet.IntVar(&args.scryptn, scryptn, configfile.ScryptDefaultLogN, "scrypt cost parameter logN. Possible values: 10-28. "+
|
||||
"A lower value speeds up mounting and reduces its memory needs, but makes the password susceptible to brute-force attacks")
|
||||
|
||||
flagSet.DurationVar(&args.idle, "i", 0, "Alias for -idle")
|
||||
flagSet.DurationVar(&args.idle, "idle", 0, "Auto-unmount after specified idle duration (ignored in reverse mode). "+
|
||||
"Durations are specified like \"500s\" or \"2h45m\". 0 means stay mounted indefinitely.")
|
||||
|
||||
var nofail bool
|
||||
flagSet.BoolVar(&nofail, "nofail", false, "Ignored for /etc/fstab compatibility")
|
||||
|
||||
var dummyString string
|
||||
flagSet.StringVar(&dummyString, "o", "", "For compatibility with mount(1), options can be also passed as a comma-separated list to -o on the end.")
|
||||
// Actual parsing
|
||||
err = flagSet.Parse(os.Args[1:])
|
||||
if err == flag.ErrHelp {
|
||||
helpShort()
|
||||
os.Exit(0)
|
||||
}
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("Invalid command line: %s. Try '%s -help'.", prettyArgs(), tlog.ProgramName)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
// We want to know if -scryptn was passed explicitly
|
||||
if isFlagPassed(flagSet, scryptn) {
|
||||
args._explicitScryptn = true
|
||||
}
|
||||
// "-openssl" needs some post-processing
|
||||
if opensslAuto == "auto" {
|
||||
args.openssl = stupidgcm.PreferOpenSSL()
|
||||
} else {
|
||||
args.openssl, err = strconv.ParseBool(opensslAuto)
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("Invalid \"-openssl\" setting: %v", err)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
}
|
||||
// "-forcedecode" only works with openssl. Check compilation and command line parameters
|
||||
if args.forcedecode == true {
|
||||
if stupidgcm.BuiltWithoutOpenssl == true {
|
||||
tlog.Fatal.Printf("The -forcedecode flag requires openssl support, but gocryptfs was compiled without it!")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
if args.aessiv == true {
|
||||
tlog.Fatal.Printf("The -forcedecode and -aessiv flags are incompatible because they use different crypto libs (openssl vs native Go)")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
if args.reverse == true {
|
||||
tlog.Fatal.Printf("The reverse mode and the -forcedecode option are not compatible")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
// Has the user explicitly disabled openssl using "-openssl=false/0"?
|
||||
if !args.openssl && opensslAuto != "auto" {
|
||||
tlog.Fatal.Printf("-forcedecode requires openssl, but is disabled via command-line option")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
args.openssl = true
|
||||
|
||||
// Try to make it harder for the user to shoot himself in the foot.
|
||||
args.ro = true
|
||||
args.allow_other = false
|
||||
args.ko = "noexec"
|
||||
}
|
||||
if !args.extpass.Empty() && len(args.passfile) != 0 {
|
||||
tlog.Fatal.Printf("The options -extpass and -passfile cannot be used at the same time")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
if len(args.passfile) != 0 && args.masterkey != "" {
|
||||
tlog.Fatal.Printf("The options -passfile and -masterkey cannot be used at the same time")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
if !args.extpass.Empty() && args.masterkey != "" {
|
||||
tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
if !args.extpass.Empty() && args.fido2 != "" {
|
||||
tlog.Fatal.Printf("The options -extpass and -fido2 cannot be used at the same time")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
if args.idle < 0 {
|
||||
tlog.Fatal.Printf("Idle timeout cannot be less than 0")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// prettyArgs pretty-prints the command-line arguments.
|
||||
func prettyArgs() string {
|
||||
pa := fmt.Sprintf("%v", os.Args)
|
||||
// Get rid of "[" and "]"
|
||||
pa = pa[1 : len(pa)-1]
|
||||
return pa
|
||||
}
|
||||
|
||||
// countOpFlags counts the number of operation flags we were passed.
|
||||
func countOpFlags(args *argContainer) int {
|
||||
var count int
|
||||
if args.info {
|
||||
count++
|
||||
}
|
||||
if args.passwd {
|
||||
count++
|
||||
}
|
||||
if args.init {
|
||||
count++
|
||||
}
|
||||
if args.fsck {
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// isFlagPassed finds out if the flag was explictely passed on the command line.
|
||||
// https://stackoverflow.com/a/54747682/1380267
|
||||
func isFlagPassed(flagSet *flag.FlagSet, name string) bool {
|
||||
found := false
|
||||
flagSet.Visit(func(f *flag.Flag) {
|
||||
if f.Name == name {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testcase struct {
|
||||
// i is the input
|
||||
i []string
|
||||
// o is the expected output
|
||||
o []string
|
||||
// Do we expect an error?
|
||||
e bool
|
||||
}
|
||||
|
||||
// TestPrefixOArgs checks that the "-o x,y,z" parsing works correctly.
|
||||
func TestPrefixOArgs(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{
|
||||
i: nil,
|
||||
o: nil,
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs"},
|
||||
o: []string{"gocryptfs"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "-v"},
|
||||
o: []string{"gocryptfs", "-v"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "foo", "bar", "-v"},
|
||||
o: []string{"gocryptfs", "foo", "bar", "-v"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "foo", "bar", "-o", "a"},
|
||||
o: []string{"gocryptfs", "-a", "foo", "bar"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "foo", "bar", "-o", "a,b,xxxxx"},
|
||||
o: []string{"gocryptfs", "-a", "-b", "-xxxxx", "foo", "bar"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "foo", "bar", "-d", "-o=a,b,xxxxx"},
|
||||
o: []string{"gocryptfs", "-a", "-b", "-xxxxx", "foo", "bar", "-d"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "foo", "bar", "-oooo", "a,b,xxxxx"},
|
||||
o: []string{"gocryptfs", "foo", "bar", "-oooo", "a,b,xxxxx"},
|
||||
},
|
||||
// https://github.com/mhogomchungu/sirikali/blob/a36d91d3e39f0c1eb9a79680ed6c28ddb6568fa8/src/siritask.cpp#L192
|
||||
{
|
||||
i: []string{"gocryptfs", "-o", "rw", "--config", "fff", "ccc", "mmm"},
|
||||
o: []string{"gocryptfs", "-rw", "--config", "fff", "ccc", "mmm"},
|
||||
},
|
||||
// "--" should also block "-o" parsing.
|
||||
{
|
||||
i: []string{"gocryptfs", "foo", "bar", "--", "-o", "a"},
|
||||
o: []string{"gocryptfs", "foo", "bar", "--", "-o", "a"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "--", "-o", "a"},
|
||||
o: []string{"gocryptfs", "--", "-o", "a"},
|
||||
},
|
||||
// This should error out
|
||||
{
|
||||
i: []string{"gocryptfs", "foo", "bar", "-o"},
|
||||
e: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
o, err := prefixOArgs(tc.i)
|
||||
e := (err != nil)
|
||||
if !reflect.DeepEqual(o, tc.o) || e != tc.e {
|
||||
t.Errorf("\n in=%q\nwant=%q err=%v\n got=%q err=%v", tc.i, tc.o, tc.e, o, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSlice(t *testing.T) {
|
||||
var s multipleStrings
|
||||
s.Set("foo")
|
||||
s.Set("bar")
|
||||
want := "[foo bar]"
|
||||
have := s.String()
|
||||
if want != have {
|
||||
t.Errorf("Wrong string representation: want=%q have=%q", want, have)
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
tenets:
|
||||
- import: codelingo/effective-go
|
||||
- import: codelingo/code-review-comments
|
||||
- import: codelingo/rfjakob-gocryptfs
|
88
common_ops.go
Normal file
88
common_ops.go
Normal file
@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"./internal/nametransform"
|
||||
"./internal/syscallcompat"
|
||||
)
|
||||
|
||||
//export gcf_get_attrs
|
||||
func gcf_get_attrs(sessionID int, relPath string) (uint64, int64, bool) {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
dirfd, cName, err := volume.prepareAtSyscall(relPath)
|
||||
if err != nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
// Translate ciphertext size to plaintext size
|
||||
size := volume.translateSize(dirfd, cName, st)
|
||||
|
||||
return size, int64(st.Mtim.Sec), true
|
||||
}
|
||||
|
||||
//export gcf_rename
|
||||
func gcf_rename(sessionID int, oldPath string, newPath string) bool {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
dirfd, cName, err := volume.prepareAtSyscall(oldPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
dirfd2, cName2, err := volume.prepareAtSyscall(newPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.Close(dirfd2)
|
||||
|
||||
// Easy case.
|
||||
if volume.plainTextNames {
|
||||
return errToBool(syscallcompat.Renameat2(dirfd, cName, dirfd2, cName2, 0))
|
||||
}
|
||||
// Long destination file name: create .name file
|
||||
nameFileAlreadyThere := false
|
||||
if nametransform.IsLongContent(cName2) {
|
||||
err = volume.nameTransform.WriteLongNameAt(dirfd2, cName2, newPath)
|
||||
// Failure to write the .name file is expected when the target path already
|
||||
// exists. Since hashes are pretty unique, there is no need to modify the
|
||||
// .name file in this case, and we ignore the error.
|
||||
if err == syscall.EEXIST {
|
||||
nameFileAlreadyThere = true
|
||||
} else if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Actual rename
|
||||
err = syscallcompat.Renameat2(dirfd, cName, dirfd2, cName2, 0)
|
||||
if err == syscall.ENOTEMPTY || err == syscall.EEXIST {
|
||||
// If an empty directory is overwritten we will always get an error as
|
||||
// the "empty" directory will still contain gocryptfs.diriv.
|
||||
// Interestingly, ext4 returns ENOTEMPTY while xfs returns EEXIST.
|
||||
// We handle that by trying to fs.Rmdir() the target directory and trying
|
||||
// again.
|
||||
if gcf_rmdir(sessionID, newPath) {
|
||||
err = syscallcompat.Renameat2(dirfd, cName, dirfd2, cName2, 0)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if nametransform.IsLongContent(cName2) && !nameFileAlreadyThere {
|
||||
// Roll back .name creation unless the .name file was already there
|
||||
nametransform.DeleteLongNameAt(dirfd2, cName2)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if nametransform.IsLongContent(cName) {
|
||||
nametransform.DeleteLongNameAt(dirfd, cName)
|
||||
}
|
||||
return true
|
||||
}
|
1
contrib/atomicrename/.gitignore
vendored
1
contrib/atomicrename/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/atomicrename
|
@ -1,101 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const fileCount = 100
|
||||
|
||||
type stats struct {
|
||||
renameOk int
|
||||
renameError int
|
||||
readOk int
|
||||
readError int
|
||||
readContentMismatch int
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Printf(`atomicrename creates %d "src" files in the current directory, renames
|
||||
them in random order over a single "dst" file while reading the "dst"
|
||||
file concurrently in a loop.
|
||||
|
||||
Progress and errors are reported as they occour in addition to a summary
|
||||
printed at the end. cifs and fuse filesystems are known to fail, local
|
||||
filesystems and nfs seem ok.
|
||||
|
||||
See https://github.com/hanwen/go-fuse/issues/398 for background info.
|
||||
`, fileCount)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
hello := []byte("hello world")
|
||||
srcFiles := make(map[string]struct{})
|
||||
|
||||
// prepare source files
|
||||
fmt.Print("creating files")
|
||||
for i := 0; i < fileCount; i++ {
|
||||
srcName := fmt.Sprintf("src.atomicrename.%d", i)
|
||||
srcFiles[srcName] = struct{}{}
|
||||
buf := bytes.Repeat([]byte("_"), i)
|
||||
buf = append(buf, hello...)
|
||||
if err := ioutil.WriteFile(srcName, buf, 0600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print(".")
|
||||
}
|
||||
fmt.Print("\n")
|
||||
|
||||
// prepare destination file
|
||||
const dstName = "dst.atomicrename"
|
||||
if err := ioutil.WriteFile(dstName, hello, 0600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var running int32 = 1
|
||||
|
||||
stats := stats{}
|
||||
|
||||
// read thread
|
||||
go func() {
|
||||
for atomic.LoadInt32(&running) == 1 {
|
||||
have, err := ioutil.ReadFile(dstName)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
stats.readError++
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(string(have), string(hello)) {
|
||||
fmt.Printf("content mismatch: have %q\n", have)
|
||||
stats.readContentMismatch++
|
||||
continue
|
||||
}
|
||||
fmt.Printf("content ok len=%d\n", len(have))
|
||||
stats.readOk++
|
||||
}
|
||||
}()
|
||||
|
||||
// rename thread = main thread
|
||||
for srcName := range srcFiles {
|
||||
if err := os.Rename(srcName, dstName); err != nil {
|
||||
fmt.Println(err)
|
||||
stats.renameError++
|
||||
}
|
||||
stats.renameOk++
|
||||
}
|
||||
// Signal the Read goroutine to stop when loop is done
|
||||
atomic.StoreInt32(&running, 0)
|
||||
|
||||
syscall.Unlink(dstName)
|
||||
fmt.Printf("stats: %#v\n", stats)
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Umount all FUSE filesystems mounted below /tmp and /var/tmp.
|
||||
#
|
||||
# Useful when you have lots of broken mounts after something in
|
||||
# the test suite went wrong.
|
||||
|
||||
set -eu
|
||||
|
||||
MOUNTS=$(mount | grep ' type fuse\.' | grep 'on /var/tmp/\|on /tmp/\|on /mnt/ext4-ramdisk/' | cut -d' ' -f 3)
|
||||
|
||||
for i in $MOUNTS ; do
|
||||
echo "Unmounting $i"
|
||||
fusermount -u -z "$i"
|
||||
done
|
1
contrib/findholes/.gitignore
vendored
1
contrib/findholes/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/findholes
|
@ -1,199 +0,0 @@
|
||||
// Package holes finds and pretty-prints holes & data sections of a file.
|
||||
// Used by TestFileHoleCopy in the gocryptfs test suite.
|
||||
package holes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SEEK_DATA = 3
|
||||
SEEK_HOLE = 4
|
||||
|
||||
SegmentHole = SegmentType(100)
|
||||
SegmentData = SegmentType(101)
|
||||
SegmentEOF = SegmentType(102)
|
||||
)
|
||||
|
||||
type Whence int
|
||||
|
||||
func (w Whence) String() string {
|
||||
switch w {
|
||||
case SEEK_DATA:
|
||||
return "SEEK_DATA"
|
||||
case SEEK_HOLE:
|
||||
return "SEEK_HOLE"
|
||||
default:
|
||||
return "???"
|
||||
}
|
||||
}
|
||||
|
||||
type Segment struct {
|
||||
Offset int64
|
||||
Type SegmentType
|
||||
}
|
||||
|
||||
func (s Segment) String() string {
|
||||
return fmt.Sprintf("%10d %v", s.Offset, s.Type)
|
||||
}
|
||||
|
||||
type SegmentType int
|
||||
|
||||
func (s SegmentType) String() string {
|
||||
switch s {
|
||||
case SegmentHole:
|
||||
return "hole"
|
||||
case SegmentData:
|
||||
return "data"
|
||||
case SegmentEOF:
|
||||
return "eof"
|
||||
default:
|
||||
return "???"
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrint pretty-prints the Segments.
|
||||
func PrettyPrint(segments []Segment) (out string) {
|
||||
for i, s := range segments {
|
||||
out += s.String()
|
||||
if i < len(segments)-1 {
|
||||
out += "\n"
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Find examines the file passed via file descriptor and returns the found holes
|
||||
// and data sections.
|
||||
func Find(fd int) (segments []Segment, err error) {
|
||||
var st syscall.Stat_t
|
||||
err = syscall.Fstat(fd, &st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
totalSize := st.Size
|
||||
|
||||
var cursor int64
|
||||
|
||||
// find out if file starts with data or hole
|
||||
off, err := syscall.Seek(fd, 0, SEEK_DATA)
|
||||
// starts with hole and has no data
|
||||
if err == syscall.ENXIO {
|
||||
segments = append(segments,
|
||||
Segment{0, SegmentHole},
|
||||
Segment{totalSize, SegmentEOF})
|
||||
return segments, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// starts with data
|
||||
if off == cursor {
|
||||
segments = append(segments, Segment{0, SegmentData})
|
||||
} else {
|
||||
// starts with hole
|
||||
segments = append(segments,
|
||||
Segment{0, SegmentHole},
|
||||
Segment{off, SegmentData})
|
||||
cursor = off
|
||||
}
|
||||
|
||||
// now we are at the start of data.
|
||||
// find next hole, then next data, then next hole, then next data...
|
||||
for {
|
||||
oldCursor := cursor
|
||||
// Next hole
|
||||
off, err = syscall.Seek(fd, cursor, SEEK_HOLE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
segments = append(segments, Segment{off, SegmentHole})
|
||||
cursor = off
|
||||
|
||||
// Next data
|
||||
off, err := syscall.Seek(fd, cursor, SEEK_DATA)
|
||||
// No more data?
|
||||
if err == syscall.ENXIO {
|
||||
segments = append(segments,
|
||||
Segment{totalSize, SegmentEOF})
|
||||
return segments, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
segments = append(segments, Segment{off, SegmentData})
|
||||
cursor = off
|
||||
|
||||
if oldCursor == cursor {
|
||||
return nil, fmt.Errorf("%s\nerror: seek loop!", PrettyPrint(segments))
|
||||
}
|
||||
}
|
||||
return segments, nil
|
||||
}
|
||||
|
||||
// Verify the gives `segments` using a full bytewise file scan
|
||||
func Verify(fd int, segments []Segment) (err error) {
|
||||
last := segments[len(segments)-1]
|
||||
if last.Type != SegmentEOF {
|
||||
log.Panicf("BUG: last segment is not EOF. segments: %v", segments)
|
||||
}
|
||||
|
||||
for i, s := range segments {
|
||||
var whence int
|
||||
switch s.Type {
|
||||
case SegmentHole:
|
||||
whence = SEEK_HOLE
|
||||
case SegmentData:
|
||||
whence = SEEK_DATA
|
||||
case SegmentEOF:
|
||||
continue
|
||||
default:
|
||||
log.Panicf("BUG: unkown segment type %d", s.Type)
|
||||
}
|
||||
for off := s.Offset; off < segments[i+1].Offset; off++ {
|
||||
res, err := syscall.Seek(fd, off, whence)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: seek(%d, %s) returned error %v", off, Whence(whence).String(), err)
|
||||
}
|
||||
if res != off {
|
||||
return fmt.Errorf("error: seek(%d, %s) returned new offset %d", off, Whence(whence).String(), res)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a test file at `path` with random holes
|
||||
func Create(path string) {
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
offsets := make([]int64, 10)
|
||||
for i := range offsets {
|
||||
offsets[i] = int64(rand.Int31n(60000))
|
||||
}
|
||||
|
||||
buf := []byte("x")
|
||||
for _, off := range offsets {
|
||||
_, err = f.WriteAt(buf, off)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expand the file to 50000 bytes so we sometimes have a hole on the end
|
||||
if offsets[len(offsets)-1] < 50000 {
|
||||
f.Truncate(50000)
|
||||
}
|
||||
|
||||
f.Sync()
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
// Find and pretty-print holes & data sections of a file.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/contrib/findholes/holes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flags := struct {
|
||||
verify *bool
|
||||
create *bool
|
||||
}{}
|
||||
flags.verify = flag.Bool("verify", false, "Verify results using full file scan")
|
||||
flags.create = flag.Bool("create", false, "Create test file with random holes")
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Printf("Usage: findholes FILE\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
path := flag.Arg(0)
|
||||
|
||||
if *flags.create {
|
||||
holes.Create(path)
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
// os.Open() gives nicer error messages than syscall.Open()
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
segments, err := holes.Find(int(f.Fd()))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println(holes.PrettyPrint(segments))
|
||||
|
||||
if *flags.verify {
|
||||
err = holes.Verify(int(f.Fd()), segments)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Println("verify ok")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1
contrib/getdents-debug/getdents/.gitignore
vendored
1
contrib/getdents-debug/getdents/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/getdents
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
Small tool to try to debug unix.Getdents problems on CIFS mounts
|
||||
( https://github.com/rfjakob/gocryptfs/issues/483 )
|
||||
|
||||
Example output:
|
||||
|
||||
$ while sleep 1 ; do ./getdents /mnt/synology/public/tmp/g1 ; done
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=3192, err=<nil>
|
||||
unix.Getdents fd3: n=0, err=<nil>
|
||||
total 24072 bytes
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=-1, err=no such file or directory
|
||||
total 16704 bytes
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=4176, err=<nil>
|
||||
unix.Getdents fd3: n=3192, err=<nil>
|
||||
unix.Getdents fd3: n=0, err=<nil>
|
||||
total 24072 bytes
|
||||
|
||||
|
||||
Failure looks like this in strace:
|
||||
|
||||
[pid 189974] getdents64(6, 0xc000105808, 10000) = -1 ENOENT (No such file or directory)
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
myName = "getdents"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s PATH\n", myName)
|
||||
fmt.Fprintf(os.Stderr, "Run getdents(2) on PATH in a 100ms loop until we hit an error\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
flag.Usage()
|
||||
}
|
||||
path := flag.Arg(0)
|
||||
|
||||
tmp := make([]byte, 10000)
|
||||
for i := 1; ; i++ {
|
||||
sum := 0
|
||||
fd, err := unix.Open(path, unix.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: unix.Open returned err=%v\n", i, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%3d: unix.Getdents: ", i)
|
||||
for {
|
||||
n, err := unix.Getdents(fd, tmp)
|
||||
fmt.Printf("n=%d; ", n)
|
||||
if n <= 0 {
|
||||
fmt.Printf("err=%v; total %d bytes\n", err, sum)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
break
|
||||
}
|
||||
sum += n
|
||||
}
|
||||
unix.Close(fd)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
1
contrib/getdents-debug/getdents_c/.gitignore
vendored
1
contrib/getdents-debug/getdents_c/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/getdents_c
|
@ -1,2 +0,0 @@
|
||||
getdents_c: *.c Makefile
|
||||
gcc getdents.c -o getdents_c
|
@ -1,53 +0,0 @@
|
||||
// See ../getdents/getdents.go for some info on why
|
||||
// this exists.
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if(argc != 2) {
|
||||
printf("Usage: %s PATH\n", argv[0]);
|
||||
printf("Run getdents(2) on PATH in a 100ms loop\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const char *path = argv[1];
|
||||
|
||||
for (int i = 1 ; ; i ++ ) {
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
printf("%3d: open: %s\n", i, strerror(errno));
|
||||
if(errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char tmp[10000];
|
||||
int sum = 0;
|
||||
printf("%3d: getdents64: ", i);
|
||||
for ( ; ; ) {
|
||||
errno = 0;
|
||||
int n = syscall(SYS_getdents64, fd, tmp, sizeof(tmp));
|
||||
printf("n=%d; ", n);
|
||||
if (n <= 0) {
|
||||
printf("errno=%d total %d bytes\n", errno, sum);
|
||||
if (n < 0) {
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
sum += n;
|
||||
}
|
||||
close(fd);
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
/readdirnames
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
Small tool to try to debug unix.Getdents problems on CIFS mounts
|
||||
( https://github.com/rfjakob/gocryptfs/issues/483 )
|
||||
|
||||
Example output:
|
||||
|
||||
$ while sleep 1 ; do ./readdirnames /mnt/synology/public/tmp/g1 ; done
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=868, err=readdirent: no such file or directory
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
2020/05/24 23:50:39 os.Open returned err=open /mnt/synology/public/tmp/g1: interrupted system call
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
Readdirnames: len=1001, err=<nil>
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
myName = "readdirnames"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s PATH\n", myName)
|
||||
fmt.Fprintf(os.Stderr, "Run os.File.Readdirnames on PATH\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
flag.Usage()
|
||||
}
|
||||
path := flag.Arg(0)
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Fatalf("os.Open returned err=%v", err)
|
||||
}
|
||||
|
||||
names, err := f.Readdirnames(0)
|
||||
fmt.Printf("Readdirnames: len=%d, err=%v\n", len(names), err)
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Conditionally try to mount a gocryptfs filesystem. If either
|
||||
# * CIPHERDIR/gocryptfs.conf does not exist OR
|
||||
# * something is already mounted on MOUNTPOINT
|
||||
# print a message to stdout (not stderr!) but exit with 0.
|
||||
#
|
||||
# This is meant to be called from automated mount systems like pam_mount,
|
||||
# where you want to avoid error messages if the filesystem does not exist,
|
||||
# or duplicate mounts if the filesystem has already been mounted.
|
||||
#
|
||||
# Note that pam_mount ignores messages on stdout which is why printing
|
||||
# to stdout is ok.
|
||||
set -eu
|
||||
MYNAME=$(basename $0)
|
||||
if [[ $# -lt 2 || $1 == -* ]]; then
|
||||
echo "Usage: $MYNAME CIPHERDIR MOUNTPOINT [-o COMMA-SEPARATED-OPTIONS]" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f $1/gocryptfs.conf ]]; then
|
||||
echo "$MYNAME: \"$1\" does not look like a gocryptfs filesystem, ignoring mount request"
|
||||
exit 0
|
||||
fi
|
||||
if mountpoint "$2" > /dev/null; then
|
||||
echo "$MYNAME: something is already mounted on \"$2\", ignoring mount request"
|
||||
exit 0
|
||||
fi
|
||||
exec gocryptfs "$@"
|
@ -1,20 +0,0 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
MNT=/mnt/ext4-ramdisk
|
||||
|
||||
if mountpoint $MNT ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IMG=$(mktemp /tmp/ext4-ramdisk-XXX.img)
|
||||
|
||||
# unlink the file when done, space will be
|
||||
# reclaimed once the fs is unmounted. Also
|
||||
# cleans up in the error case.
|
||||
trap 'rm "$IMG"' EXIT
|
||||
|
||||
dd if=/dev/zero of="$IMG" bs=1M count=1030 status=none
|
||||
mkfs.ext4 -q "$IMG"
|
||||
mkdir -p "$MNT"
|
||||
mount "$IMG" "$MNT"
|
||||
chmod 777 "$MNT"
|
1
contrib/statfs/.gitignore
vendored
1
contrib/statfs/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/statfs
|
@ -1,35 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
myName = "statfs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s PATH\n", myName)
|
||||
fmt.Fprintf(os.Stderr, "Dump the statfs information for PATH to the console, JSON format.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
flag.Usage()
|
||||
}
|
||||
path := flag.Arg(0)
|
||||
var st unix.Statfs_t
|
||||
err := unix.Statfs(path, &st)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "statfs syscall returned error: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
jsn, _ := json.MarshalIndent(st, "", "\t")
|
||||
fmt.Println(string(jsn))
|
||||
}
|
1
contrib/statvsfstat/.gitignore
vendored
1
contrib/statvsfstat/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/statvsfstat
|
@ -1,53 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
myName = "statvsfstat"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s PATH\n", myName)
|
||||
fmt.Fprintf(os.Stderr, "Dump the stat and fstat information for PATH to the console, JSON format.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
flag.Usage()
|
||||
}
|
||||
path := flag.Arg(0)
|
||||
|
||||
var st unix.Stat_t
|
||||
err := unix.Stat(path, &st)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "stat syscall returned error: %v\n", err)
|
||||
os.Exit(4)
|
||||
}
|
||||
|
||||
fd, err := unix.Open(path, unix.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "open syscall returned error: %v\n", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
var fst unix.Stat_t
|
||||
err = unix.Fstat(fd, &fst)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fstat syscall returned error: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
fmt.Printf("stat result: %#v\n", st)
|
||||
fmt.Printf("fstat result: %#v\n", fst)
|
||||
if st == fst {
|
||||
fmt.Println("results are identical")
|
||||
} else {
|
||||
fmt.Println("results differ")
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
#!/bin/bash -eu
|
||||
#
|
||||
# Build on all supported architectures & operating systems
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
export GO111MODULE=on
|
||||
B="go build -tags without_openssl"
|
||||
|
||||
set -x
|
||||
|
||||
export CGO_ENABLED=0
|
||||
|
||||
GOOS=linux GOARCH=amd64 $B
|
||||
|
||||
# See https://github.com/golang/go/wiki/GoArm
|
||||
GOOS=linux GOARCH=arm GOARM=7 $B
|
||||
GOOS=linux GOARCH=arm64 $B
|
||||
|
||||
# MacOS on Intel
|
||||
GOOS=darwin GOARCH=amd64 $B
|
||||
|
||||
# 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
|
||||
fi
|
||||
|
||||
# The cross-built binary is not useful on the compile host.
|
||||
rm gocryptfs
|
@ -1,61 +0,0 @@
|
||||
// Package ctlsock is a Go library that can be used to query the
|
||||
// gocryptfs control socket interface. This interface can be
|
||||
// activated by passing `-ctlsock /tmp/my.sock` to gocryptfs on the
|
||||
// command line.
|
||||
// See gocryptfs-xray for a usage example.
|
||||
package ctlsock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (r *ResponseStruct) Error() string {
|
||||
return fmt.Sprintf("errno %d: %s", r.ErrNo, r.ErrText)
|
||||
}
|
||||
|
||||
// CtlSock encapsulates a control socket
|
||||
type CtlSock struct {
|
||||
Conn net.Conn
|
||||
}
|
||||
|
||||
// New opens the socket at `socketPath` and stores it in a `CtlSock` object.
|
||||
func New(socketPath string) (*CtlSock, error) {
|
||||
conn, err := net.DialTimeout("unix", socketPath, 1*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CtlSock{Conn: conn}, nil
|
||||
}
|
||||
|
||||
// Query sends a request to the control socket returns the response.
|
||||
func (c *CtlSock) Query(req *RequestStruct) (*ResponseStruct, error) {
|
||||
c.Conn.SetDeadline(time.Now().Add(time.Second))
|
||||
msg, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = c.Conn.Write(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, 5000)
|
||||
n, err := c.Conn.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = buf[:n]
|
||||
var resp ResponseStruct
|
||||
json.Unmarshal(buf, &resp)
|
||||
if resp.ErrNo != 0 {
|
||||
return nil, &resp
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Close closes the socket
|
||||
func (c *CtlSock) Close() {
|
||||
c.Conn.Close()
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package ctlsock
|
||||
|
||||
// RequestStruct is sent by a client (encoded as JSON).
|
||||
// You cannot perform both encryption and decryption in the same request.
|
||||
type RequestStruct struct {
|
||||
// EncryptPath is the path that should be encrypted.
|
||||
EncryptPath string
|
||||
// DecryptPath is the path that should be decrypted.
|
||||
DecryptPath string
|
||||
}
|
||||
|
||||
// ResponseStruct is sent by the server in response to a request
|
||||
// (encoded as JSON).
|
||||
type ResponseStruct struct {
|
||||
// Result is the resulting decrypted or encrypted path. Empty on error.
|
||||
Result string
|
||||
// ErrNo is the error number as defined in errno.h.
|
||||
// 0 means success and -1 means that the error number is not known
|
||||
// (look at ErrText in this case).
|
||||
ErrNo int32
|
||||
// ErrText is a detailed error message.
|
||||
ErrText string
|
||||
// WarnText contains warnings that may have been encountered while
|
||||
// processing the message.
|
||||
WarnText string
|
||||
}
|
106
daemonize.go
106
daemonize.go
@ -1,106 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
// The child sends us USR1 if the mount was successful. Exit with error code
|
||||
// 0 if we get it.
|
||||
func exitOnUsr1() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGUSR1)
|
||||
go func() {
|
||||
<-c
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
// forkChild - execute ourselves once again, this time with the "-fg" flag, and
|
||||
// wait for SIGUSR1 or child exit.
|
||||
// This is a workaround for the missing true fork function in Go.
|
||||
func forkChild() int {
|
||||
name := os.Args[0]
|
||||
// Use the full path to our executable if we can get if from /proc.
|
||||
buf := make([]byte, syscallcompat.PATH_MAX)
|
||||
n, err := syscall.Readlink("/proc/self/exe", buf)
|
||||
if err == nil {
|
||||
name = string(buf[:n])
|
||||
tlog.Debug.Printf("forkChild: readlink worked: %q", name)
|
||||
}
|
||||
newArgs := []string{"-fg", fmt.Sprintf("-notifypid=%d", os.Getpid())}
|
||||
newArgs = append(newArgs, os.Args[1:]...)
|
||||
c := exec.Command(name, newArgs...)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
c.Stdin = os.Stdin
|
||||
exitOnUsr1()
|
||||
err = c.Start()
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("forkChild: starting %s failed: %v", name, err)
|
||||
return exitcodes.ForkChild
|
||||
}
|
||||
err = c.Wait()
|
||||
if err != nil {
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if waitstat, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
os.Exit(waitstat.ExitStatus())
|
||||
}
|
||||
}
|
||||
tlog.Fatal.Printf("forkChild: wait returned an unknown error: %v", err)
|
||||
return exitcodes.ForkChild
|
||||
}
|
||||
// The child exited with 0 - let's do the same.
|
||||
return 0
|
||||
}
|
||||
|
||||
// redirectStdFds redirects stderr and stdout to syslog; stdin to /dev/null
|
||||
func redirectStdFds() {
|
||||
// Create a pipe pair "pw" -> "pr" and start logger reading from "pr".
|
||||
// We do it ourselves instead of using StdinPipe() because we need access
|
||||
// to the fd numbers.
|
||||
pr, pw, err := os.Pipe()
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("redirectStdFds: could not create pipe: %v\n", err)
|
||||
return
|
||||
}
|
||||
tag := fmt.Sprintf("gocryptfs-%d-logger", os.Getpid())
|
||||
cmd := exec.Command("logger", "-t", tag)
|
||||
cmd.Stdin = pr
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("redirectStdFds: could not start logger: %v\n", err)
|
||||
return
|
||||
}
|
||||
// The logger now reads on "pr". We can close it.
|
||||
pr.Close()
|
||||
// Redirect stout and stderr to "pw".
|
||||
err = syscallcompat.Dup3(int(pw.Fd()), 1, 0)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("redirectStdFds: stdout dup error: %v\n", err)
|
||||
}
|
||||
syscallcompat.Dup3(int(pw.Fd()), 2, 0)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("redirectStdFds: stderr dup error: %v\n", err)
|
||||
}
|
||||
// Our stout and stderr point to "pw". We can close the extra copy.
|
||||
pw.Close()
|
||||
// Redirect stdin to /dev/null
|
||||
nullFd, err := os.Open("/dev/null")
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("redirectStdFds: could not open /dev/null: %v\n", err)
|
||||
return
|
||||
}
|
||||
err = syscallcompat.Dup3(int(nullFd.Fd()), 0, 0)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("redirectStdFds: stdin dup error: %v\n", err)
|
||||
}
|
||||
nullFd.Close()
|
||||
}
|
242
directory.go
Normal file
242
directory.go
Normal file
@ -0,0 +1,242 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"./allocator"
|
||||
"./internal/configfile"
|
||||
"./internal/cryptocore"
|
||||
"./internal/nametransform"
|
||||
"./internal/syscallcompat"
|
||||
)
|
||||
|
||||
func mkdirWithIv(dirfd int, cName string, mode uint32) error {
|
||||
// Between the creation of the directory and the creation of gocryptfs.diriv
|
||||
// the directory is inconsistent. Take the lock to prevent other readers
|
||||
// from seeing it.
|
||||
err := unix.Mkdirat(dirfd, cName, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscallcompat.O_PATH, 0)
|
||||
if err == nil {
|
||||
// Create gocryptfs.diriv
|
||||
err = nametransform.WriteDirIVAt(dirfd2)
|
||||
syscall.Close(dirfd2)
|
||||
}
|
||||
if err != nil {
|
||||
// Delete inconsistent directory (missing gocryptfs.diriv!)
|
||||
syscallcompat.Unlinkat(dirfd, cName, unix.AT_REMOVEDIR)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//export gcf_list_dir
|
||||
func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
parentDirFd, cDirName, err := volume.prepareAtSyscall(dirName)
|
||||
if err != nil {
|
||||
return nil, nil, 0
|
||||
}
|
||||
defer syscall.Close(parentDirFd)
|
||||
// Read ciphertext directory
|
||||
fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return nil, nil, 0
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
cipherEntries, err := syscallcompat.Getdents(fd)
|
||||
if err != nil {
|
||||
return nil, nil, 0
|
||||
}
|
||||
// Get DirIV (stays nil if PlaintextNames is used)
|
||||
var cachedIV []byte
|
||||
if !OpenedVolumes[sessionID].plainTextNames {
|
||||
// Read the DirIV from disk
|
||||
cachedIV, err = nametransform.ReadDirIVAt(fd)
|
||||
if err != nil {
|
||||
return nil, nil, 0
|
||||
}
|
||||
}
|
||||
// Decrypted directory entries
|
||||
var plain strings.Builder
|
||||
var modes []uint32
|
||||
// Filter and decrypt filenames
|
||||
for i := range cipherEntries {
|
||||
cName := cipherEntries[i].Name
|
||||
if dirName == "" && cName == configfile.ConfDefaultName {
|
||||
// silently ignore "gocryptfs.conf" in the top level dir
|
||||
continue
|
||||
}
|
||||
if OpenedVolumes[sessionID].plainTextNames {
|
||||
plain.WriteString(cipherEntries[i].Name + "\x00")
|
||||
modes = append(modes, cipherEntries[i].Mode)
|
||||
continue
|
||||
}
|
||||
if cName == nametransform.DirIVFilename {
|
||||
// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
|
||||
continue
|
||||
}
|
||||
// Handle long file name
|
||||
isLong := nametransform.NameType(cName)
|
||||
if isLong == nametransform.LongNameContent {
|
||||
cNameLong, err := nametransform.ReadLongNameAt(fd, cName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
cName = cNameLong
|
||||
} else if isLong == nametransform.LongNameFilename {
|
||||
// ignore "gocryptfs.longname.*.name"
|
||||
continue
|
||||
}
|
||||
name, err := OpenedVolumes[sessionID].nameTransform.DecryptName(cName, cachedIV)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Override the ciphertext name with the plaintext name but reuse the rest
|
||||
// of the structure
|
||||
cipherEntries[i].Name = name
|
||||
plain.WriteString(cipherEntries[i].Name + "\x00")
|
||||
modes = append(modes, cipherEntries[i].Mode)
|
||||
}
|
||||
p := allocator.Malloc(len(modes))
|
||||
for i := 0; i < len(modes); i++ {
|
||||
offset := C.sizeof_int * uintptr(i)
|
||||
*(*C.int)(unsafe.Pointer(uintptr(p) + offset)) = (C.int)(modes[i])
|
||||
}
|
||||
return C.CString(plain.String()), (*C.int)(p), (C.int)(len(modes))
|
||||
}
|
||||
|
||||
//export gcf_mkdir
|
||||
func gcf_mkdir(sessionID int, path string, mode uint32) bool {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
dirfd, cName, err := volume.prepareAtSyscall(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
if volume.plainTextNames {
|
||||
err = unix.Mkdirat(dirfd, cName, mode)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var ust unix.Stat_t
|
||||
err = syscallcompat.Fstatat(dirfd, cName, &ust, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// We need write and execute permissions to create gocryptfs.diriv.
|
||||
// Also, we need read permissions to open the directory (to avoid
|
||||
// race-conditions between getting and setting the mode).
|
||||
origMode := mode
|
||||
mode := mode | 0700
|
||||
|
||||
// Handle long file name
|
||||
if nametransform.IsLongContent(cName) {
|
||||
// Create ".name"
|
||||
err = OpenedVolumes[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Create directory
|
||||
err = mkdirWithIv(dirfd, cName, mode)
|
||||
if err != nil {
|
||||
nametransform.DeleteLongNameAt(dirfd, cName)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
err = mkdirWithIv(dirfd, cName, mode)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fd, err := syscallcompat.Openat(dirfd, cName,
|
||||
syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
var st syscall.Stat_t
|
||||
err = syscall.Fstat(fd, &st)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Fix permissions
|
||||
if origMode != mode {
|
||||
// Preserve SGID bit if it was set due to inheritance.
|
||||
origMode = uint32(st.Mode&^0777) | origMode
|
||||
syscall.Fchmod(fd, origMode)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//export gcf_rmdir
|
||||
func gcf_rmdir(sessionID int, relPath string) bool {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
parentDirFd, cName, err := volume.openBackingDir(relPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.Close(parentDirFd)
|
||||
if volume.plainTextNames {
|
||||
// Unlinkat with AT_REMOVEDIR is equivalent to Rmdir
|
||||
err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
|
||||
return errToBool(err)
|
||||
}
|
||||
dirfd, err := syscallcompat.Openat(parentDirFd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
// Check directory contents
|
||||
children, err := syscallcompat.Getdents(dirfd)
|
||||
if err == io.EOF {
|
||||
// The directory is empty
|
||||
err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
|
||||
return errToBool(err)
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// If the directory is not empty besides gocryptfs.diriv, do not even
|
||||
// attempt the dance around gocryptfs.diriv.
|
||||
if len(children) > 1 {
|
||||
return false
|
||||
}
|
||||
// Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ"
|
||||
tmpName := fmt.Sprintf("%s.rmdir.%d", nametransform.DirIVFilename, cryptocore.RandUint64())
|
||||
err = syscallcompat.Renameat(dirfd, nametransform.DirIVFilename, parentDirFd, tmpName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Actual Rmdir
|
||||
err = syscallcompat.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
|
||||
if err != nil {
|
||||
// This can happen if another file in the directory was created in the
|
||||
// meantime, undo the rename
|
||||
syscallcompat.Renameat(parentDirFd, tmpName, dirfd, nametransform.DirIVFilename)
|
||||
return errToBool(err)
|
||||
}
|
||||
// Delete "gocryptfs.diriv.rmdir.XYZ"
|
||||
syscallcompat.Unlinkat(parentDirFd, tmpName, 0)
|
||||
// Delete .name file
|
||||
if nametransform.IsLongContent(cName) {
|
||||
nametransform.DeleteLongNameAt(parentDirFd, cName)
|
||||
}
|
||||
return true
|
||||
}
|
488
file.go
Normal file
488
file.go
Normal file
@ -0,0 +1,488 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"./internal/contentenc"
|
||||
"./internal/nametransform"
|
||||
"./internal/syscallcompat"
|
||||
)
|
||||
|
||||
// mangleOpenFlags is used by Create() and Open() to convert the open flags the user
|
||||
// wants to the flags we internally use to open the backing file.
|
||||
// The returned flags always contain O_NOFOLLOW.
|
||||
func mangleOpenFlags(flags uint32) (newFlags int) {
|
||||
newFlags = int(flags)
|
||||
// Convert WRONLY to RDWR. We always need read access to do read-modify-write cycles.
|
||||
if (newFlags & syscall.O_ACCMODE) == syscall.O_WRONLY {
|
||||
newFlags = newFlags ^ os.O_WRONLY | os.O_RDWR
|
||||
}
|
||||
// We also cannot open the file in append mode, we need to seek back for RMW
|
||||
newFlags = newFlags &^ os.O_APPEND
|
||||
// O_DIRECT accesses must be aligned in both offset and length. Due to our
|
||||
// crypto header, alignment will be off, even if userspace makes aligned
|
||||
// accesses. Running xfstests generic/013 on ext4 used to trigger lots of
|
||||
// EINVAL errors due to missing alignment. Just fall back to buffered IO.
|
||||
newFlags = newFlags &^ syscallcompat.O_DIRECT
|
||||
// Create and Open are two separate FUSE operations, so O_CREAT should not
|
||||
// be part of the open flags.
|
||||
newFlags = newFlags &^ syscall.O_CREAT
|
||||
// We always want O_NOFOLLOW to be safe against symlink races
|
||||
newFlags |= syscall.O_NOFOLLOW
|
||||
return newFlags
|
||||
}
|
||||
|
||||
func (volume *Volume) registerFileHandle(file File) int {
|
||||
handleID := -1
|
||||
c := 0
|
||||
for handleID == -1 {
|
||||
_, ok := volume.file_handles[c]
|
||||
if !ok {
|
||||
handleID = c
|
||||
}
|
||||
c++
|
||||
}
|
||||
volume.file_handles[handleID] = file
|
||||
return handleID
|
||||
}
|
||||
|
||||
// readFileID loads the file header from disk and extracts the file ID.
|
||||
// Returns io.EOF if the file is empty.
|
||||
func readFileID(fd *os.File) ([]byte, error) {
|
||||
// We read +1 byte to determine if the file has actual content
|
||||
// and not only the header. A header-only file will be considered empty.
|
||||
// This makes File ID poisoning more difficult.
|
||||
readLen := contentenc.HeaderLen + 1
|
||||
buf := make([]byte, readLen)
|
||||
_, err := fd.ReadAt(buf, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = buf[:contentenc.HeaderLen]
|
||||
h, err := contentenc.ParseHeader(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.ID, nil
|
||||
}
|
||||
|
||||
// createHeader creates a new random header and writes it to disk.
|
||||
// Returns the new file ID.
|
||||
// The caller must hold fileIDLock.Lock().
|
||||
func createHeader(fd *os.File) (fileID []byte, err error) {
|
||||
h := contentenc.RandomHeader()
|
||||
buf := h.Pack()
|
||||
// Prevent partially written (=corrupt) header by preallocating the space beforehand
|
||||
err = syscallcompat.EnospcPrealloc(int(fd.Fd()), 0, contentenc.HeaderLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Actually write header
|
||||
_, err = fd.WriteAt(buf, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.ID, err
|
||||
}
|
||||
|
||||
// doRead - read "length" plaintext bytes from plaintext offset "off" and append
|
||||
// to "dst".
|
||||
// Arguments "length" and "off" do not have to be block-aligned.
|
||||
//
|
||||
// doRead reads the corresponding ciphertext blocks from disk, decrypts them and
|
||||
// returns the requested part of the plaintext.
|
||||
//
|
||||
// Called by Read() for normal reading,
|
||||
// by Write() and Truncate() via doWrite() for Read-Modify-Write.
|
||||
func (volume *Volume) doRead(handleID int, dst []byte, off uint64, length uint64) ([]byte, bool) {
|
||||
f, ok := volume.file_handles[handleID]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
fd := f.fd
|
||||
// Get the file ID, either from the open file table, or from disk.
|
||||
var fileID []byte
|
||||
if volume.fileIDs[handleID] != nil {
|
||||
// Use the cached value in the file table
|
||||
fileID = volume.fileIDs[handleID]
|
||||
} else {
|
||||
// Not cached, we have to read it from disk.
|
||||
var err error
|
||||
fileID, err = readFileID(fd)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
// Save into the file table
|
||||
volume.fileIDs[handleID] = fileID
|
||||
}
|
||||
|
||||
// Read the backing ciphertext in one go
|
||||
blocks := volume.contentEnc.ExplodePlainRange(off, length)
|
||||
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
|
||||
// f.fd.ReadAt takes an int64!
|
||||
if alignedOffset > math.MaxInt64 {
|
||||
return nil, false
|
||||
}
|
||||
skip := blocks[0].Skip
|
||||
|
||||
ciphertext := volume.contentEnc.CReqPool.Get()
|
||||
ciphertext = ciphertext[:int(alignedLength)]
|
||||
n, err := fd.ReadAt(ciphertext, int64(alignedOffset))
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, false
|
||||
}
|
||||
// The ReadAt came back empty. We can skip all the decryption and return early.
|
||||
if n == 0 {
|
||||
volume.contentEnc.CReqPool.Put(ciphertext)
|
||||
return dst, true
|
||||
}
|
||||
// Truncate ciphertext buffer down to actually read bytes
|
||||
ciphertext = ciphertext[0:n]
|
||||
|
||||
firstBlockNo := blocks[0].BlockNo
|
||||
|
||||
// Decrypt it
|
||||
plaintext, err := volume.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
|
||||
volume.contentEnc.CReqPool.Put(ciphertext)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Crop down to the relevant part
|
||||
var out []byte
|
||||
lenHave := len(plaintext)
|
||||
lenWant := int(skip + length)
|
||||
if lenHave > lenWant {
|
||||
out = plaintext[skip:lenWant]
|
||||
} else if lenHave > int(skip) {
|
||||
out = plaintext[skip:lenHave]
|
||||
}
|
||||
// else: out stays empty, file was smaller than the requested offset
|
||||
|
||||
out = append(dst, out...)
|
||||
volume.contentEnc.PReqPool.Put(plaintext)
|
||||
|
||||
return out, true
|
||||
}
|
||||
|
||||
// doWrite - encrypt "data" and write it to plaintext offset "off"
|
||||
//
|
||||
// Arguments do not have to be block-aligned, read-modify-write is
|
||||
// performed internally as necessary
|
||||
//
|
||||
// Called by Write() for normal writing,
|
||||
// and by Truncate() to rewrite the last file block.
|
||||
//
|
||||
// Empty writes do nothing and are allowed.
|
||||
func (volume *Volume) doWrite(handleID int, data []byte, off uint64) (uint32, bool) {
|
||||
fileWasEmpty := false
|
||||
// Get the file ID, create a new one if it does not exist yet.
|
||||
var fileID []byte
|
||||
|
||||
f, ok := volume.file_handles[handleID]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
fd := f.fd
|
||||
// The caller has exclusively locked ContentLock, which blocks all other
|
||||
// readers and writers. No need to take IDLock.
|
||||
if volume.fileIDs[handleID] != nil {
|
||||
fileID = volume.fileIDs[handleID]
|
||||
} else {
|
||||
// If the file ID is not cached, read it from disk
|
||||
var err error
|
||||
fileID, err = readFileID(fd)
|
||||
// Write a new file header if the file is empty
|
||||
if err == io.EOF {
|
||||
fileID, err = createHeader(fd)
|
||||
fileWasEmpty = true
|
||||
}
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
volume.fileIDs[handleID] = fileID
|
||||
}
|
||||
// Handle payload data
|
||||
dataBuf := bytes.NewBuffer(data)
|
||||
blocks := volume.contentEnc.ExplodePlainRange(off, uint64(len(data)))
|
||||
toEncrypt := make([][]byte, len(blocks))
|
||||
for i, b := range blocks {
|
||||
blockData := dataBuf.Next(int(b.Length))
|
||||
// Incomplete block -> Read-Modify-Write
|
||||
if b.IsPartial() {
|
||||
// Read
|
||||
oldData, success := volume.doRead(handleID, nil, b.BlockPlainOff(), volume.contentEnc.PlainBS())
|
||||
if !success {
|
||||
return 0, false
|
||||
}
|
||||
// Modify
|
||||
blockData = volume.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip))
|
||||
}
|
||||
// Write into the to-encrypt list
|
||||
toEncrypt[i] = blockData
|
||||
}
|
||||
// Encrypt all blocks
|
||||
ciphertext := volume.contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, fileID)
|
||||
// Preallocate so we cannot run out of space in the middle of the write.
|
||||
// This prevents partially written (=corrupt) blocks.
|
||||
var err error
|
||||
cOff := blocks[0].BlockCipherOff()
|
||||
// f.fd.WriteAt & syscallcompat.EnospcPrealloc take int64 offsets!
|
||||
if cOff > math.MaxInt64 {
|
||||
return 0, false
|
||||
}
|
||||
err = syscallcompat.EnospcPrealloc(int(fd.Fd()), int64(cOff), int64(len(ciphertext)))
|
||||
if err != nil {
|
||||
if fileWasEmpty {
|
||||
// Kill the file header again
|
||||
syscall.Ftruncate(int(fd.Fd()), 0)
|
||||
gcf_close_file(volume.volumeID, handleID)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
// Write
|
||||
_, err = f.fd.WriteAt(ciphertext, int64(cOff))
|
||||
// Return memory to CReqPool
|
||||
volume.contentEnc.CReqPool.Put(ciphertext)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return uint32(len(data)), true
|
||||
}
|
||||
|
||||
// Zero-pad the file of size plainSize to the next block boundary. This is a no-op
|
||||
// if the file is already block-aligned.
|
||||
func (volume *Volume) zeroPad(handleID int, plainSize uint64) bool {
|
||||
lastBlockLen := plainSize % volume.contentEnc.PlainBS()
|
||||
if lastBlockLen == 0 {
|
||||
// Already block-aligned
|
||||
return true
|
||||
}
|
||||
missing := volume.contentEnc.PlainBS() - lastBlockLen
|
||||
pad := make([]byte, missing)
|
||||
_, success := volume.doWrite(handleID, pad, plainSize)
|
||||
return success
|
||||
}
|
||||
|
||||
// truncateGrowFile extends a file using seeking or ftruncate performing RMW on
|
||||
// the first and last block as necessary. New blocks in the middle become
|
||||
// file holes unless they have been fallocate()'d beforehand.
|
||||
func (volume *Volume) truncateGrowFile(handleID int, oldPlainSz uint64, newPlainSz uint64) bool {
|
||||
if newPlainSz <= oldPlainSz {
|
||||
return false
|
||||
}
|
||||
newEOFOffset := newPlainSz - 1
|
||||
if oldPlainSz > 0 {
|
||||
n1 := volume.contentEnc.PlainOffToBlockNo(oldPlainSz - 1)
|
||||
n2 := volume.contentEnc.PlainOffToBlockNo(newEOFOffset)
|
||||
// The file is grown within one block, no need to pad anything.
|
||||
// Write a single zero to the last byte and let doWrite figure out the RMW.
|
||||
if n1 == n2 {
|
||||
buf := make([]byte, 1)
|
||||
_, success := volume.doWrite(handleID, buf, newEOFOffset)
|
||||
return success
|
||||
}
|
||||
}
|
||||
// The truncate creates at least one new block.
|
||||
//
|
||||
// Make sure the old last block is padded to the block boundary. This call
|
||||
// is a no-op if it is already block-aligned.
|
||||
success := volume.zeroPad(handleID, oldPlainSz)
|
||||
if !success {
|
||||
return false
|
||||
}
|
||||
// The new size is block-aligned. In this case we can do everything ourselves
|
||||
// and avoid the call to doWrite.
|
||||
if newPlainSz%volume.contentEnc.PlainBS() == 0 {
|
||||
// The file was empty, so it did not have a header. Create one.
|
||||
if oldPlainSz == 0 {
|
||||
id, err := createHeader(volume.file_handles[handleID].fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
volume.fileIDs[handleID] = id
|
||||
}
|
||||
cSz := int64(volume.contentEnc.PlainSizeToCipherSize(newPlainSz))
|
||||
err := syscall.Ftruncate(int(volume.file_handles[handleID].fd.Fd()), cSz)
|
||||
return errToBool(err)
|
||||
}
|
||||
// The new size is NOT aligned, so we need to write a partial block.
|
||||
// Write a single zero to the last byte and let doWrite figure it out.
|
||||
buf := make([]byte, 1)
|
||||
_, success = volume.doWrite(handleID, buf, newEOFOffset)
|
||||
return success
|
||||
}
|
||||
|
||||
func (volume *Volume) truncate(handleID int, newSize uint64) bool {
|
||||
fileFD := int(volume.file_handles[handleID].fd.Fd())
|
||||
var err error
|
||||
// Common case first: Truncate to zero
|
||||
if newSize == 0 {
|
||||
err = syscall.Ftruncate(fileFD, 0)
|
||||
return err == nil
|
||||
}
|
||||
// We need the old file size to determine if we are growing or shrinking
|
||||
// the file
|
||||
oldSize, _, success := gcf_get_attrs(volume.volumeID, volume.file_handles[handleID].path)
|
||||
if !success {
|
||||
return false
|
||||
}
|
||||
|
||||
// File size stays the same - nothing to do
|
||||
if newSize == oldSize {
|
||||
return true
|
||||
}
|
||||
// File grows
|
||||
if newSize > oldSize {
|
||||
return volume.truncateGrowFile(handleID, oldSize, newSize)
|
||||
}
|
||||
|
||||
// File shrinks
|
||||
blockNo := volume.contentEnc.PlainOffToBlockNo(newSize)
|
||||
cipherOff := volume.contentEnc.BlockNoToCipherOff(blockNo)
|
||||
plainOff := volume.contentEnc.BlockNoToPlainOff(blockNo)
|
||||
lastBlockLen := newSize - plainOff
|
||||
var data []byte
|
||||
if lastBlockLen > 0 {
|
||||
data, success = volume.doRead(handleID, nil, plainOff, lastBlockLen)
|
||||
if !success {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Truncate down to the last complete block
|
||||
err = syscall.Ftruncate(fileFD, int64(cipherOff))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Append partial block
|
||||
if lastBlockLen > 0 {
|
||||
_, success := volume.doWrite(handleID, data, plainOff)
|
||||
return success
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//export gcf_open_read_mode
|
||||
func gcf_open_read_mode(sessionID int, path string) int {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
dirfd, cName, err := volume.prepareAtSyscall(path)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
// Open backing file
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, mangleOpenFlags(0), 0)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return volume.registerFileHandle(File{os.NewFile(uintptr(fd), cName), path})
|
||||
}
|
||||
|
||||
//export gcf_open_write_mode
|
||||
func gcf_open_write_mode(sessionID int, path string, mode uint32) int {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
dirfd, cName, err := volume.prepareAtSyscall(path)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
fd := -1
|
||||
newFlags := mangleOpenFlags(syscall.O_RDWR)
|
||||
// Handle long file name
|
||||
if !volume.plainTextNames && nametransform.IsLongContent(cName) {
|
||||
// Create ".name"
|
||||
err = volume.nameTransform.WriteLongNameAt(dirfd, cName, path)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
// Create content
|
||||
fd, err = syscallcompat.Openat(dirfd, cName, newFlags|syscall.O_CREAT, mode)
|
||||
if err != nil {
|
||||
nametransform.DeleteLongNameAt(dirfd, cName)
|
||||
}
|
||||
} else {
|
||||
// Create content, normal (short) file name
|
||||
fd, err = syscallcompat.Openat(dirfd, cName, newFlags|syscall.O_CREAT, mode)
|
||||
}
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return volume.registerFileHandle(File{os.NewFile(uintptr(fd), cName), path})
|
||||
}
|
||||
|
||||
//export gcf_truncate
|
||||
func gcf_truncate(sessionID int, handleID int, offset uint64) bool {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
return volume.truncate(handleID, offset)
|
||||
}
|
||||
|
||||
//export gcf_read_file
|
||||
func gcf_read_file(sessionID, handleID int, offset uint64, dst_buff []byte) uint32 {
|
||||
length := len(dst_buff)
|
||||
if length > contentenc.MAX_KERNEL_WRITE {
|
||||
// This would crash us due to our fixed-size buffer pool
|
||||
return 0
|
||||
}
|
||||
|
||||
volume := OpenedVolumes[sessionID]
|
||||
out, success := volume.doRead(handleID, dst_buff[:0], offset, uint64(length))
|
||||
if !success {
|
||||
return 0
|
||||
} else {
|
||||
return uint32(len(out))
|
||||
}
|
||||
}
|
||||
|
||||
//export gcf_write_file
|
||||
func gcf_write_file(sessionID, handleID int, offset uint64, data []byte) uint32 {
|
||||
length := len(data)
|
||||
if length > contentenc.MAX_KERNEL_WRITE {
|
||||
// This would crash us due to our fixed-size buffer pool
|
||||
return 0
|
||||
}
|
||||
|
||||
volume := OpenedVolumes[sessionID]
|
||||
n, _ := volume.doWrite(handleID, data, offset)
|
||||
return n
|
||||
}
|
||||
|
||||
//export gcf_close_file
|
||||
func gcf_close_file(sessionID, handleID int) {
|
||||
f, ok := OpenedVolumes[sessionID].file_handles[handleID]
|
||||
if ok {
|
||||
f.fd.Close()
|
||||
delete(OpenedVolumes[sessionID].file_handles, handleID)
|
||||
_, ok := OpenedVolumes[sessionID].fileIDs[handleID]
|
||||
if ok {
|
||||
delete(OpenedVolumes[sessionID].fileIDs, handleID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export gcf_remove_file
|
||||
func gcf_remove_file(sessionID int, path string) bool {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
dirfd, cName, err := volume.prepareAtSyscall(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
// Delete content
|
||||
err = syscallcompat.Unlinkat(dirfd, cName, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Delete ".name" file
|
||||
if !volume.plainTextNames && nametransform.IsLongContent(cName) {
|
||||
err = nametransform.DeleteLongNameAt(dirfd, cName)
|
||||
}
|
||||
return errToBool(err)
|
||||
}
|
346
fsck.go
346
fsck.go
@ -1,346 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
type fsckObj struct {
|
||||
rootNode *fusefrontend.RootNode
|
||||
// mnt is the mountpoint of the temporary mount
|
||||
mnt string
|
||||
// List of corrupt files
|
||||
corruptList []string
|
||||
// List of skipped files
|
||||
skippedList []string
|
||||
// Protects corruptList
|
||||
listLock sync.Mutex
|
||||
// stop a running watchMitigatedCorruptions thread
|
||||
watchDone chan struct{}
|
||||
// Inode numbers of hard-linked files (Nlink > 1) that we have already checked
|
||||
seenInodes map[uint64]struct{}
|
||||
// abort the running fsck operation? Checked in a few long-running loops.
|
||||
abort bool
|
||||
}
|
||||
|
||||
func runsAsRoot() bool {
|
||||
return syscall.Geteuid() == 0
|
||||
}
|
||||
|
||||
func (ck *fsckObj) markCorrupt(path string) {
|
||||
ck.listLock.Lock()
|
||||
ck.corruptList = append(ck.corruptList, path)
|
||||
ck.listLock.Unlock()
|
||||
}
|
||||
|
||||
func (ck *fsckObj) markSkipped(path string) {
|
||||
ck.listLock.Lock()
|
||||
ck.skippedList = append(ck.skippedList, path)
|
||||
ck.listLock.Unlock()
|
||||
}
|
||||
|
||||
func (ck *fsckObj) abs(relPath string) (absPath string) {
|
||||
return filepath.Join(ck.mnt, relPath)
|
||||
}
|
||||
|
||||
// Watch for mitigated corruptions that occur during OpenDir()
|
||||
func (ck *fsckObj) watchMitigatedCorruptionsOpenDir(path string) {
|
||||
for {
|
||||
select {
|
||||
case item := <-ck.rootNode.MitigatedCorruptions:
|
||||
fmt.Printf("fsck: corrupt entry in dir %q: %q\n", path, item)
|
||||
ck.markCorrupt(filepath.Join(path, item))
|
||||
case <-ck.watchDone:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check dir for corruption
|
||||
func (ck *fsckObj) dir(relPath string) {
|
||||
tlog.Debug.Printf("ck.dir %q\n", relPath)
|
||||
ck.xattrs(relPath)
|
||||
// Run OpenDir and catch transparently mitigated corruptions
|
||||
go ck.watchMitigatedCorruptionsOpenDir(relPath)
|
||||
f, err := os.Open(ck.abs(relPath))
|
||||
ck.watchDone <- struct{}{}
|
||||
if err != nil {
|
||||
fmt.Printf("fsck: error opening dir %q: %v\n", relPath, err)
|
||||
if err == os.ErrPermission && !runsAsRoot() {
|
||||
ck.markSkipped(relPath)
|
||||
} else {
|
||||
ck.markCorrupt(relPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
go ck.watchMitigatedCorruptionsOpenDir(relPath)
|
||||
entries, err := f.Readdirnames(0)
|
||||
ck.watchDone <- struct{}{}
|
||||
if err != nil {
|
||||
fmt.Printf("fsck: error reading dir %q: %v\n", relPath, err)
|
||||
ck.markCorrupt(relPath)
|
||||
return
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if ck.abort {
|
||||
return
|
||||
}
|
||||
if entry == "." || entry == ".." {
|
||||
continue
|
||||
}
|
||||
nextPath := filepath.Join(relPath, entry)
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Lstat(ck.abs(nextPath), &st)
|
||||
if err != nil {
|
||||
ck.markCorrupt(filepath.Join(relPath, entry))
|
||||
continue
|
||||
}
|
||||
filetype := st.Mode & syscall.S_IFMT
|
||||
//fmt.Printf(" %q %x\n", entry.Name, entry.Mode)
|
||||
switch filetype {
|
||||
case syscall.S_IFDIR:
|
||||
ck.dir(nextPath)
|
||||
case syscall.S_IFREG:
|
||||
ck.file(nextPath)
|
||||
case syscall.S_IFLNK:
|
||||
ck.symlink(nextPath)
|
||||
case syscall.S_IFIFO, syscall.S_IFSOCK, syscall.S_IFBLK, syscall.S_IFCHR:
|
||||
// nothing to check
|
||||
default:
|
||||
fmt.Printf("fsck: unhandled file type %x\n", filetype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ck *fsckObj) symlink(relPath string) {
|
||||
_, err := os.Readlink(ck.abs(relPath))
|
||||
if err != nil {
|
||||
ck.markCorrupt(relPath)
|
||||
fmt.Printf("fsck: error reading symlink %q: %v\n", relPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for mitigated corruptions that occur during Read()
|
||||
func (ck *fsckObj) watchMitigatedCorruptionsRead(path string) {
|
||||
for {
|
||||
select {
|
||||
case item := <-ck.rootNode.MitigatedCorruptions:
|
||||
fmt.Printf("fsck: corrupt file %q (inode %s)\n", path, item)
|
||||
ck.markCorrupt(path)
|
||||
case <-ck.watchDone:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check file for corruption
|
||||
func (ck *fsckObj) file(relPath string) {
|
||||
tlog.Debug.Printf("ck.file %q\n", relPath)
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Lstat(ck.abs(relPath), &st)
|
||||
if err != nil {
|
||||
ck.markCorrupt(relPath)
|
||||
fmt.Printf("fsck: error stating file %q: %v\n", relPath, err)
|
||||
return
|
||||
}
|
||||
if st.Nlink > 1 {
|
||||
// Due to hard links, we may have already checked this file.
|
||||
if _, ok := ck.seenInodes[st.Ino]; ok {
|
||||
tlog.Debug.Printf("ck.file : skipping %q (inode number %d already seen)\n", relPath, st.Ino)
|
||||
return
|
||||
}
|
||||
ck.seenInodes[st.Ino] = struct{}{}
|
||||
}
|
||||
ck.xattrs(relPath)
|
||||
f, err := os.Open(ck.abs(relPath))
|
||||
if err != nil {
|
||||
fmt.Printf("fsck: error opening file %q: %v\n", relPath, err)
|
||||
if err == os.ErrPermission && !runsAsRoot() {
|
||||
ck.markSkipped(relPath)
|
||||
} else {
|
||||
ck.markCorrupt(relPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
// 128 kiB of zeros
|
||||
allZero := make([]byte, fuse.MAX_KERNEL_WRITE)
|
||||
buf := make([]byte, fuse.MAX_KERNEL_WRITE)
|
||||
var off int64
|
||||
// Read() through the whole file and catch transparently mitigated corruptions
|
||||
go ck.watchMitigatedCorruptionsRead(relPath)
|
||||
defer func() { ck.watchDone <- struct{}{} }()
|
||||
for {
|
||||
if ck.abort {
|
||||
return
|
||||
}
|
||||
tlog.Debug.Printf("ck.file: read %d bytes from offset %d\n", len(buf), off)
|
||||
n, err := f.ReadAt(buf, off)
|
||||
if err != nil && err != io.EOF {
|
||||
ck.markCorrupt(relPath)
|
||||
fmt.Printf("fsck: error reading file %q (inum %d): %v\n", relPath, inum(f), err)
|
||||
return
|
||||
}
|
||||
// EOF
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
off += int64(n)
|
||||
// If we seem to be in the middle of a file hole, try to skip to the next
|
||||
// data section.
|
||||
data := buf[:n]
|
||||
if bytes.Equal(data, allZero) {
|
||||
tlog.Debug.Printf("ck.file: trying to skip file hole\n")
|
||||
const SEEK_DATA = 3
|
||||
nextOff, err := syscall.Seek(int(f.Fd()), off, SEEK_DATA)
|
||||
if err == nil {
|
||||
off = nextOff
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for mitigated corruptions that occur during ListXAttr()
|
||||
func (ck *fsckObj) watchMitigatedCorruptionsListXAttr(path string) {
|
||||
for {
|
||||
select {
|
||||
case item := <-ck.rootNode.MitigatedCorruptions:
|
||||
fmt.Printf("fsck: corrupt xattr name on file %q: %q\n", path, item)
|
||||
ck.markCorrupt(path + " xattr:" + item)
|
||||
case <-ck.watchDone:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check xattrs on file/dir at path
|
||||
func (ck *fsckObj) xattrs(relPath string) {
|
||||
// Run ListXAttr() and catch transparently mitigated corruptions
|
||||
go ck.watchMitigatedCorruptionsListXAttr(relPath)
|
||||
attrs, err := syscallcompat.Llistxattr(ck.abs(relPath))
|
||||
ck.watchDone <- struct{}{}
|
||||
if err != nil {
|
||||
fmt.Printf("fsck: error listing xattrs on %q: %v\n", relPath, err)
|
||||
ck.markCorrupt(relPath)
|
||||
return
|
||||
}
|
||||
// Try to read all xattr values
|
||||
for _, a := range attrs {
|
||||
_, err := syscallcompat.Lgetxattr(ck.abs(relPath), a)
|
||||
if err != nil {
|
||||
fmt.Printf("fsck: error reading xattr %q from %q: %v\n", a, relPath, err)
|
||||
if err == syscall.EACCES && !runsAsRoot() {
|
||||
ck.markSkipped(relPath)
|
||||
} else {
|
||||
ck.markCorrupt(relPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// entrypoint from main()
|
||||
func fsck(args *argContainer) (exitcode int) {
|
||||
if args.reverse {
|
||||
tlog.Fatal.Printf("Running -fsck with -reverse is not supported")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
args.allow_other = false
|
||||
args.ro = true
|
||||
var err error
|
||||
args.mountpoint, err = ioutil.TempDir("", "gocryptfs.fsck.")
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("fsck: TmpDir: %v", err)
|
||||
os.Exit(exitcodes.MountPoint)
|
||||
}
|
||||
pfs, wipeKeys := initFuseFrontend(args)
|
||||
rn := pfs.(*fusefrontend.RootNode)
|
||||
rn.MitigatedCorruptions = make(chan string)
|
||||
ck := fsckObj{
|
||||
mnt: args.mountpoint,
|
||||
rootNode: rn,
|
||||
watchDone: make(chan struct{}),
|
||||
seenInodes: make(map[uint64]struct{}),
|
||||
}
|
||||
if args.quiet {
|
||||
// go-fuse throws a lot of these:
|
||||
// writer: Write/Writev failed, err: 2=no such file or directory. opcode: INTERRUPT
|
||||
// This is ugly and causes failures in xfstests. Hide them away in syslog.
|
||||
tlog.SwitchLoggerToSyslog()
|
||||
}
|
||||
// Mount
|
||||
srv := initGoFuse(pfs, args)
|
||||
// Handle SIGINT & SIGTERM
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, os.Interrupt)
|
||||
signal.Notify(ch, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-ch
|
||||
ck.abort = true
|
||||
}()
|
||||
defer func() {
|
||||
err = srv.Unmount()
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("failed to unmount %q: %v", ck.mnt, err)
|
||||
} else {
|
||||
if err := syscall.Rmdir(ck.mnt); err != nil {
|
||||
tlog.Warn.Printf("cleaning up %q failed: %v", ck.mnt, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Recursively check the root dir
|
||||
ck.dir("")
|
||||
// Report results
|
||||
wipeKeys()
|
||||
if ck.abort {
|
||||
tlog.Info.Printf("fsck: aborted")
|
||||
return exitcodes.Other
|
||||
}
|
||||
if len(ck.corruptList) == 0 && len(ck.skippedList) == 0 {
|
||||
tlog.Info.Printf("fsck summary: no problems found\n")
|
||||
return 0
|
||||
}
|
||||
if len(ck.skippedList) > 0 {
|
||||
tlog.Warn.Printf("fsck: re-run this program as root to check all files!\n")
|
||||
}
|
||||
fmt.Printf("fsck summary: %d corrupt files, %d files skipped\n", len(ck.corruptList), len(ck.skippedList))
|
||||
return exitcodes.FsckErrors
|
||||
}
|
||||
|
||||
type sortableDirEntries []fuse.DirEntry
|
||||
|
||||
func (s sortableDirEntries) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s sortableDirEntries) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s sortableDirEntries) Less(i, j int) bool {
|
||||
return strings.Compare(s[i].Name, s[j].Name) < 0
|
||||
}
|
||||
|
||||
func inum(f *os.File) uint64 {
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Fstat(int(f.Fd()), &st)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("inum: fstat failed: %v", err)
|
||||
return 0
|
||||
}
|
||||
return st.Ino
|
||||
}
|
19
go.mod
19
go.mod
@ -1,19 +0,0 @@
|
||||
module github.com/rfjakob/gocryptfs
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/hanwen/go-fuse/v2 v2.1.1-0.20210508151621-62c5aa1919a7
|
||||
github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115
|
||||
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd // indirect
|
||||
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff // indirect
|
||||
github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 // indirect
|
||||
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb // indirect
|
||||
github.com/pkg/xattr v0.4.1
|
||||
github.com/rfjakob/eme v1.1.1
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94
|
||||
github.com/stretchr/testify v1.5.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3
|
||||
)
|
48
go.sum
48
go.sum
@ -1,48 +0,0 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
|
||||
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
|
||||
github.com/hanwen/go-fuse/v2 v2.1.1-0.20210508151621-62c5aa1919a7 h1:9K/MBPvPptwwCYIw8gBi/Sup5Uw8UeYlyKBxxzl931Y=
|
||||
github.com/hanwen/go-fuse/v2 v2.1.1-0.20210508151621-62c5aa1919a7/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc=
|
||||
github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115 h1:YuDUUFNM21CAbyPOpOP8BicaTD/0klJEKt5p8yuw+uY=
|
||||
github.com/jacobsa/crypto v0.0.0-20190317225127-9f44e2d11115/go.mod h1:LadVJg0XuawGk+8L1rYnIED8451UyNxEMdTWCEt5kmU=
|
||||
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA=
|
||||
github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M=
|
||||
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw=
|
||||
github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff/go.mod h1:gJWba/XXGl0UoOmBQKRWCJdHrr3nE0T65t6ioaj3mLI=
|
||||
github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 h1:BMb8s3ENQLt5ulwVIHVDWFHp8eIXmbfSExkvdn9qMXI=
|
||||
github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11/go.mod h1:+DBdDyfoO2McrOyDemRBq0q9CMEByef7sYl7JH5Q3BI=
|
||||
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb h1:uSWBjJdMf47kQlXMwWEfmc864bA1wAC+Kl3ApryuG9Y=
|
||||
github.com/jacobsa/reqtrace v0.0.0-20150505043853-245c9e0234cb/go.mod h1:ivcmUvxXWjb27NsPEaiYK7AidlZXS7oQ5PowUS9z3I4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/pkg/xattr v0.4.1 h1:dhclzL6EqOXNaPDWqoeb9tIxATfBSmjqL0b4DpSjwRw=
|
||||
github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rfjakob/eme v1.1.1 h1:t+CgvcOn+eDvj2xdglxsSnkgg8LM8jwdxnV7OnsrTn0=
|
||||
github.com/rfjakob/eme v1.1.1/go.mod h1:U2bmx0hDj8EyDdcxmD5t3XHDnBFnyNNc22n1R4008eM=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
1
gocryptfs-xray/.gitignore
vendored
1
gocryptfs-xray/.gitignore
vendored
@ -1 +0,0 @@
|
||||
gocryptfs-xray
|
@ -1,62 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/ctlsock"
|
||||
)
|
||||
|
||||
func decryptPaths(socketPath string, sep0 bool) {
|
||||
var req ctlsock.RequestStruct
|
||||
transformPaths(socketPath, &req, &req.DecryptPath, sep0)
|
||||
}
|
||||
|
||||
func encryptPaths(socketPath string, sep0 bool) {
|
||||
var req ctlsock.RequestStruct
|
||||
transformPaths(socketPath, &req, &req.EncryptPath, sep0)
|
||||
}
|
||||
|
||||
func transformPaths(socketPath string, req *ctlsock.RequestStruct, in *string, sep0 bool) {
|
||||
errorCount := 0
|
||||
c, err := ctlsock.New(socketPath)
|
||||
if err != nil {
|
||||
fmt.Printf("fatal: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
line := 1
|
||||
var separator byte = '\n'
|
||||
if sep0 {
|
||||
separator = '\000'
|
||||
}
|
||||
r := bufio.NewReader(os.Stdin)
|
||||
for eof := false; eof == false; line++ {
|
||||
val, err := r.ReadBytes(separator)
|
||||
if len(val) == 0 {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// break the loop after we have processed the last val
|
||||
eof = true
|
||||
} else {
|
||||
// drop trailing separator
|
||||
val = val[:len(val)-1]
|
||||
}
|
||||
*in = string(val)
|
||||
resp, err := c.Query(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error at input line %d %q: %v\n", line, *in, err)
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
if resp.WarnText != "" {
|
||||
fmt.Fprintf(os.Stderr, "warning at input line %d %q: %v\n", line, *in, resp.WarnText)
|
||||
}
|
||||
fmt.Printf("%s%c", resp.Result, separator)
|
||||
}
|
||||
if errorCount == 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/fido2"
|
||||
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
// GitVersion is the gocryptfs version according to git, set by build.bash
|
||||
var GitVersion = "[GitVersion not set - please compile using ./build.bash]"
|
||||
|
||||
// GitVersionFuse is the go-fuse library version, set by build.bash
|
||||
var GitVersionFuse = "[GitVersionFuse not set - please compile using ./build.bash]"
|
||||
|
||||
// BuildDate is a date string like "2017-09-06", set by build.bash
|
||||
var BuildDate = "0000-00-00"
|
||||
|
||||
const (
|
||||
ivLen = contentenc.DefaultIVBits / 8
|
||||
authTagLen = cryptocore.AuthTagLen
|
||||
blockSize = contentenc.DefaultBS + ivLen + cryptocore.AuthTagLen
|
||||
myName = "gocryptfs-xray"
|
||||
)
|
||||
|
||||
func errExit(err error) {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func prettyPrintHeader(h *contentenc.FileHeader, aessiv bool) {
|
||||
id := hex.EncodeToString(h.ID)
|
||||
msg := "Header: Version: %d, Id: %s"
|
||||
if aessiv {
|
||||
msg += ", assuming AES-SIV mode"
|
||||
} else {
|
||||
msg += ", assuming AES-GCM mode"
|
||||
}
|
||||
fmt.Printf(msg+"\n", h.Version, id)
|
||||
}
|
||||
|
||||
// printVersion prints a version string like this:
|
||||
// gocryptfs v1.7-32-gcf99cfd; go-fuse v1.0.0-174-g22a9cb9; 2019-05-12 go1.12 linux/amd64
|
||||
func printVersion() {
|
||||
built := fmt.Sprintf("%s %s", BuildDate, runtime.Version())
|
||||
fmt.Printf("%s %s; %s %s/%s\n",
|
||||
myName, GitVersion, built,
|
||||
runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
printVersion()
|
||||
fmt.Printf("\n")
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] FILE\n"+
|
||||
"\n"+
|
||||
"Options:\n", myName)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, "\n"+
|
||||
"Examples:\n"+
|
||||
" gocryptfs-xray myfs/mCXnISiv7nEmyc0glGuhTQ\n"+
|
||||
" gocryptfs-xray -dumpmasterkey myfs/gocryptfs.conf\n"+
|
||||
" gocryptfs-xray -encrypt-paths myfs.sock\n")
|
||||
}
|
||||
|
||||
// sum counts the number of true values
|
||||
func sum(x ...*bool) (s int) {
|
||||
for _, v := range x {
|
||||
if *v {
|
||||
s++
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func main() {
|
||||
var args struct {
|
||||
dumpmasterkey *bool
|
||||
decryptPaths *bool
|
||||
encryptPaths *bool
|
||||
aessiv *bool
|
||||
sep0 *bool
|
||||
fido2 *string
|
||||
version *bool
|
||||
}
|
||||
args.dumpmasterkey = flag.Bool("dumpmasterkey", false, "Decrypt and dump the master key")
|
||||
args.decryptPaths = flag.Bool("decrypt-paths", false, "Decrypt file paths using gocryptfs control socket")
|
||||
args.encryptPaths = flag.Bool("encrypt-paths", false, "Encrypt file paths using gocryptfs control socket")
|
||||
args.sep0 = flag.Bool("0", false, "Use \\0 instead of \\n as separator")
|
||||
args.aessiv = flag.Bool("aessiv", false, "Assume AES-SIV mode instead of AES-GCM")
|
||||
args.fido2 = flag.String("fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
|
||||
args.version = flag.Bool("version", false, "Print version information")
|
||||
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if *args.version {
|
||||
printVersion()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
s := sum(args.dumpmasterkey, args.decryptPaths, args.encryptPaths)
|
||||
if s > 1 {
|
||||
fmt.Printf("fatal: %d operations were requested\n", s)
|
||||
os.Exit(1)
|
||||
}
|
||||
if flag.NArg() != 1 {
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
fn := flag.Arg(0)
|
||||
if *args.decryptPaths {
|
||||
decryptPaths(fn, *args.sep0)
|
||||
}
|
||||
if *args.encryptPaths {
|
||||
encryptPaths(fn, *args.sep0)
|
||||
}
|
||||
fd, err := os.Open(fn)
|
||||
if err != nil {
|
||||
errExit(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
if *args.dumpmasterkey {
|
||||
dumpMasterKey(fn, *args.fido2)
|
||||
} else {
|
||||
inspectCiphertext(fd, *args.aessiv)
|
||||
}
|
||||
}
|
||||
|
||||
func dumpMasterKey(fn string, fido2Path string) {
|
||||
tlog.Info.Enabled = false
|
||||
cf, err := configfile.Load(fn)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
exitcodes.Exit(err)
|
||||
}
|
||||
var pw []byte
|
||||
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
|
||||
if fido2Path == "" {
|
||||
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
pw = fido2.Secret(fido2Path, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
|
||||
} else {
|
||||
pw = readpassword.Once(nil, nil, "")
|
||||
}
|
||||
masterkey, err := cf.DecryptMasterKey(pw)
|
||||
fmt.Println(hex.EncodeToString(masterkey))
|
||||
for i := range pw {
|
||||
pw[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func inspectCiphertext(fd *os.File, aessiv bool) {
|
||||
headerBytes := make([]byte, contentenc.HeaderLen)
|
||||
n, err := fd.ReadAt(headerBytes, 0)
|
||||
if err == io.EOF && n == 0 {
|
||||
fmt.Println("empty file")
|
||||
os.Exit(0)
|
||||
} else if err == io.EOF {
|
||||
fmt.Printf("incomplete file header: read %d bytes, want %d\n", n, contentenc.HeaderLen)
|
||||
os.Exit(1)
|
||||
} else if err != nil {
|
||||
errExit(err)
|
||||
}
|
||||
header, err := contentenc.ParseHeader(headerBytes)
|
||||
if err != nil {
|
||||
errExit(err)
|
||||
}
|
||||
prettyPrintHeader(header, aessiv)
|
||||
var i int64
|
||||
buf := make([]byte, blockSize)
|
||||
for i = 0; ; i++ {
|
||||
off := contentenc.HeaderLen + i*blockSize
|
||||
n, err := fd.ReadAt(buf, off)
|
||||
if err != nil && err != io.EOF {
|
||||
errExit(err)
|
||||
}
|
||||
if n == 0 && err == io.EOF {
|
||||
break
|
||||
}
|
||||
// A block contains at least the IV, the Auth Tag and 1 data byte
|
||||
if n < ivLen+authTagLen+1 {
|
||||
errExit(fmt.Errorf("corrupt block: truncated data, len=%d", n))
|
||||
}
|
||||
data := buf[:n]
|
||||
// Parse block data
|
||||
iv := data[:ivLen]
|
||||
tag := data[len(data)-authTagLen:]
|
||||
if aessiv {
|
||||
tag = data[ivLen : ivLen+authTagLen]
|
||||
}
|
||||
fmt.Printf("Block %2d: IV: %s, Tag: %s, Offset: %5d Len: %d\n",
|
||||
i, hex.EncodeToString(iv), hex.EncodeToString(tag), off, len(data))
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
Your master key is:
|
||||
|
||||
b4d8b25c-324dd6ea-a328c990-6e8a2a3c-
|
||||
6038552a-042ced43-26cfff21-0c62957a
|
@ -1,3 +0,0 @@
|
||||
Header: Version: 2, Id: 8932adf303fe0289679d47fa84d2b241, assuming AES-GCM mode
|
||||
Block 0: IV: c8536b4bfd92f5dc3c1e2ac29f116d4a, Tag: 22b20422749b2f4bba67ec7d3bb1ac34, Offset: 18 Len: 4128
|
||||
Block 1: IV: 2de68f4965779bb137ef2b3c20453556, Tag: 3e8758d6872234b1fffab2504e623467, Offset: 4146 Len: 936
|
Binary file not shown.
@ -1,20 +0,0 @@
|
||||
{
|
||||
"Creator": "gocryptfs v1.7-beta1-11-g8d71f8f-dirty",
|
||||
"EncryptedKey": "Rp0VYTJ9QK2imhJQH1miFIgAYZbsfv3t1tvPJvsOVy86ogBzKpUuMDFXD+PPawLvZM/TuYl0n3gx1RY5hzFfbg==",
|
||||
"ScryptObject": {
|
||||
"Salt": "mDPjzd+SZpScPYVv/M9AFjNzXcUy6fqKckXay53EQdQ=",
|
||||
"N": 1024,
|
||||
"R": 8,
|
||||
"P": 1,
|
||||
"KeyLen": 32
|
||||
},
|
||||
"Version": 2,
|
||||
"FeatureFlags": [
|
||||
"GCMIV128",
|
||||
"HKDF",
|
||||
"DirIV",
|
||||
"EMENames",
|
||||
"LongNames",
|
||||
"Raw64"
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
™<EFBFBD>:őjµ‹Í‰/î\<5C>W„
|
@ -1,4 +0,0 @@
|
||||
Your master key is:
|
||||
|
||||
83dfe2df-9f6ad754-179cb4d5-377a65dd-
|
||||
bff54950-10e1b7b5-faf409e3-f7d8eeee
|
@ -1,3 +0,0 @@
|
||||
Header: Version: 2, Id: d839806747918e345633fcdd0988e67c, assuming AES-SIV mode
|
||||
Block 0: IV: 1d3ce2b13260f83766ccf9a670478a4b, Tag: 0b6f95bd523b4c93704e15ecc6bef8e7, Offset: 18 Len: 4128
|
||||
Block 1: IV: 7eb947d2adf18adf3bed39bbc8052968, Tag: 1a272903e5a987f53f07344840387c20, Offset: 4146 Len: 936
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"Creator": "gocryptfs v1.7-beta1-11-g8d71f8f-dirty",
|
||||
"EncryptedKey": "YOxpZ+cImv4HirwuwIUpRmOMlyAFRvEqHOXdgpMcGvIlm70h4q+shSr3RZ19xomnbFZXGfIfKQ2APtVYWOAwuw==",
|
||||
"ScryptObject": {
|
||||
"Salt": "OzdcVESNmkD0403NHBWezQmq2SyDyLOY2/B4Aev2lHc=",
|
||||
"N": 1024,
|
||||
"R": 8,
|
||||
"P": 1,
|
||||
"KeyLen": 32
|
||||
},
|
||||
"Version": 2,
|
||||
"FeatureFlags": [
|
||||
"GCMIV128",
|
||||
"HKDF",
|
||||
"DirIV",
|
||||
"EMENames",
|
||||
"LongNames",
|
||||
"Raw64",
|
||||
"AESSIV"
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
<EFBFBD>_<EFBFBD>5<EFBFBD><EFBFBD><EFBFBD><EFBFBD>p<EFBFBD><EFBFBD><EFBFBD><EFBFBD><0C>F
|
Binary file not shown.
@ -1,110 +0,0 @@
|
||||
package xray_tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/tests/test_helpers"
|
||||
)
|
||||
|
||||
func TestAesgcmXray(t *testing.T) {
|
||||
expected, err := ioutil.ReadFile("aesgcm_fs.xray.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd := exec.Command("../gocryptfs-xray", "aesgcm_fs/VnvoeSetPaOFjZDaZAh0lA")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes.Compare(out, expected) != 0 {
|
||||
t.Errorf("Unexpected output")
|
||||
fmt.Printf("expected:\n%s", string(expected))
|
||||
fmt.Printf("have:\n%s", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAessivXray(t *testing.T) {
|
||||
expected, err := ioutil.ReadFile("aessiv_fs.xray.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd := exec.Command("../gocryptfs-xray", "-aessiv", "aessiv_fs/klepPXQJIaEDaIx-yurAqQ")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes.Compare(out, expected) != 0 {
|
||||
t.Errorf("Unexpected output")
|
||||
fmt.Printf("expected:\n%s", string(expected))
|
||||
fmt.Printf("have:\n%s", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpmasterkey(t *testing.T) {
|
||||
expected := "b4d8b25c324dd6eaa328c9906e8a2a3c6038552a042ced4326cfff210c62957a\n"
|
||||
cmd := exec.Command("../gocryptfs-xray", "-dumpmasterkey", "aesgcm_fs/gocryptfs.conf")
|
||||
// Password = "test"
|
||||
cmd.Stdin = bytes.NewBuffer([]byte("test"))
|
||||
out1, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := string(out1)
|
||||
if out != expected {
|
||||
t.Errorf("Wrong output")
|
||||
fmt.Printf("expected: %s\n", expected)
|
||||
fmt.Printf("have: %s\n", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptPaths(t *testing.T) {
|
||||
cDir := test_helpers.InitFS(t)
|
||||
pDir := cDir + ".mnt"
|
||||
sock := cDir + ".sock"
|
||||
test_helpers.MountOrFatal(t, cDir, pDir, "-ctlsock="+sock, "-extpass", "echo test")
|
||||
defer test_helpers.UnmountPanic(pDir)
|
||||
|
||||
testCases := []struct {
|
||||
in []string
|
||||
sep0 bool
|
||||
}{
|
||||
{
|
||||
[]string{
|
||||
"test1",
|
||||
"test1\n",
|
||||
"test1\ntest2",
|
||||
"test1\ntest2\n",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{
|
||||
"test1",
|
||||
"test1\000",
|
||||
"test1\000test2",
|
||||
"test1\000test2\000",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
for _, in := range tc.in {
|
||||
sepArg := "-0=false"
|
||||
if tc.sep0 {
|
||||
sepArg = "-0=true"
|
||||
}
|
||||
cmd := exec.Command("../gocryptfs-xray", "-encrypt-paths", sepArg, sock)
|
||||
cmd.Stdin = bytes.NewBuffer([]byte(in))
|
||||
out, err := cmd.CombinedOutput()
|
||||
t.Logf("%q", string(out))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
golint.bash
17
golint.bash
@ -1,17 +0,0 @@
|
||||
#!/bin/bash -u
|
||||
|
||||
OUTPUT=$(
|
||||
golint ./... | \
|
||||
grep -v "don't use an underscore in package name" | \
|
||||
grep -v "don't use ALL_CAPS in Go names; use CamelCase" |
|
||||
grep -v "don't use underscores in Go names"
|
||||
)
|
||||
|
||||
# No output --> all good
|
||||
if [[ -z $OUTPUT ]] ; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "golint.bash:"
|
||||
echo "$OUTPUT"
|
||||
exit 1
|
56
help.go
56
help.go
@ -1,56 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
const tUsage = "" +
|
||||
"Usage: " + tlog.ProgramName + " -init|-passwd|-info [OPTIONS] CIPHERDIR\n" +
|
||||
" or " + tlog.ProgramName + " [OPTIONS] CIPHERDIR MOUNTPOINT\n"
|
||||
|
||||
// helpShort is what gets displayed when passed "-h" or on syntax error.
|
||||
func helpShort() {
|
||||
printVersion()
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf(tUsage)
|
||||
fmt.Printf(`
|
||||
Common Options (use -hh to show all):
|
||||
-aessiv Use AES-SIV encryption (with -init)
|
||||
-allow_other Allow other users to access the mount
|
||||
-i, -idle Unmount automatically after specified idle duration
|
||||
-config Custom path to config file
|
||||
-ctlsock Create control socket at location
|
||||
-extpass Call external program to prompt for the password
|
||||
-fg Stay in the foreground
|
||||
-fsck Check filesystem integrity
|
||||
-fusedebug Debug FUSE calls
|
||||
-h, -help This short help text
|
||||
-hh Long help text with all options
|
||||
-init Initialize encrypted directory
|
||||
-info Display information about encrypted directory
|
||||
-masterkey Mount with explicit master key instead of password
|
||||
-nonempty Allow mounting over non-empty directory
|
||||
-nosyslog Do not redirect log messages to syslog
|
||||
-passfile Read password from plain text file(s)
|
||||
-passwd Change password
|
||||
-plaintextnames Do not encrypt file names (with -init)
|
||||
-q, -quiet Silence informational messages
|
||||
-reverse Enable reverse mode
|
||||
-ro Mount read-only
|
||||
-speed Run crypto speed test
|
||||
-version Print version information
|
||||
-- Stop option parsing
|
||||
`)
|
||||
}
|
||||
|
||||
// helpLong gets only displayed on "-hh"
|
||||
func helpLong() {
|
||||
printVersion()
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf(tUsage)
|
||||
fmt.Printf("\nOptions:\n")
|
||||
flagSet.PrintDefaults()
|
||||
fmt.Printf(" --\n Stop option parsing\n")
|
||||
}
|
183
helpers.go
Normal file
183
helpers.go
Normal file
@ -0,0 +1,183 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"./internal/configfile"
|
||||
"./internal/nametransform"
|
||||
"./internal/syscallcompat"
|
||||
)
|
||||
|
||||
// isFiltered - check if plaintext "path" should be forbidden
|
||||
//
|
||||
// Prevents name clashes with internal files when file names are not encrypted
|
||||
func (volume *Volume) isFiltered(path string) bool {
|
||||
if !volume.plainTextNames {
|
||||
return false
|
||||
}
|
||||
// gocryptfs.conf in the root directory is forbidden
|
||||
if path == configfile.ConfDefaultName {
|
||||
return true
|
||||
}
|
||||
// Note: gocryptfs.diriv is NOT forbidden because diriv and plaintextnames
|
||||
// are exclusive
|
||||
return false
|
||||
}
|
||||
|
||||
func (volume *Volume) openBackingDir(relPath string) (dirfd int, cName string, err error) {
|
||||
dirRelPath := nametransform.Dir(relPath)
|
||||
// With PlaintextNames, we don't need to read DirIVs. Easy.
|
||||
if volume.plainTextNames {
|
||||
dirfd, err = syscallcompat.OpenDirNofollow(volume.rootCipherDir, dirRelPath)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
// If relPath is empty, cName is ".".
|
||||
cName = filepath.Base(relPath)
|
||||
return dirfd, cName, nil
|
||||
}
|
||||
// Open cipherdir (following symlinks)
|
||||
dirfd, err = syscallcompat.Open(volume.rootCipherDir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
// If relPath is empty, cName is ".".
|
||||
if relPath == "" {
|
||||
return dirfd, ".", nil
|
||||
}
|
||||
// Walk the directory tree
|
||||
parts := strings.Split(relPath, "/")
|
||||
for i, name := range parts {
|
||||
iv, err := nametransform.ReadDirIVAt(dirfd)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
cName, err = volume.nameTransform.EncryptAndHashName(name, iv)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
// Last part? We are done.
|
||||
if i == len(parts)-1 {
|
||||
break
|
||||
}
|
||||
// Not the last part? Descend into next directory.
|
||||
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
syscall.Close(dirfd)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
dirfd = dirfd2
|
||||
}
|
||||
return dirfd, cName, nil
|
||||
}
|
||||
|
||||
func (volume *Volume) prepareAtSyscall(path string) (dirfd int, cName string, err error) {
|
||||
// root node itself is special
|
||||
if path == "" {
|
||||
return volume.openBackingDir(path)
|
||||
}
|
||||
|
||||
// Cache lookup
|
||||
// TODO make it work for plaintextnames as well?
|
||||
if !volume.plainTextNames {
|
||||
directory, ok := volume.dirCache[path]
|
||||
if ok {
|
||||
if directory.fd > 0 {
|
||||
cName, err := volume.nameTransform.EncryptAndHashName(filepath.Base(path), directory.iv)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
dirfd, err = syscall.Dup(directory.fd)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
return dirfd, cName, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slowpath
|
||||
if volume.isFiltered(path) {
|
||||
return -1, "", syscall.EPERM
|
||||
}
|
||||
dirfd, cName, err = volume.openBackingDir(path)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
// Cache store
|
||||
if !volume.plainTextNames {
|
||||
// TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work?
|
||||
iv, err := nametransform.ReadDirIVAt(dirfd)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
dirfdDup, err := syscall.Dup(dirfd)
|
||||
if err == nil {
|
||||
var pathCopy strings.Builder
|
||||
pathCopy.WriteString(path)
|
||||
volume.dirCache[pathCopy.String()] = Directory{dirfdDup, iv}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// decryptSymlinkTarget: "cData64" is base64-decoded and decrypted
|
||||
// like file contents (GCM).
|
||||
// The empty string decrypts to the empty string.
|
||||
//
|
||||
// This function does not do any I/O and is hence symlink-safe.
|
||||
func (volume *Volume) decryptSymlinkTarget(cData64 string) (string, error) {
|
||||
if cData64 == "" {
|
||||
return "", nil
|
||||
}
|
||||
cData, err := volume.nameTransform.B64DecodeString(cData64)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := volume.contentEnc.DecryptBlock([]byte(cData), 0, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// readlink reads and decrypts a symlink. Used by Readlink, Getattr, Lookup.
|
||||
func (volume *Volume) readlink(dirfd int, cName string) []byte {
|
||||
cTarget, err := syscallcompat.Readlinkat(dirfd, cName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if volume.plainTextNames {
|
||||
return []byte(cTarget)
|
||||
}
|
||||
// Symlinks are encrypted like file contents (GCM) and base64-encoded
|
||||
target, err := volume.decryptSymlinkTarget(cTarget)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return []byte(target)
|
||||
}
|
||||
|
||||
func isRegular(mode uint32) bool { return (mode & syscall.S_IFMT) == syscall.S_IFREG }
|
||||
|
||||
func isSymlink(mode uint32) bool { return (mode & syscall.S_IFMT) == syscall.S_IFLNK }
|
||||
|
||||
// translateSize translates the ciphertext size in `out` into plaintext size.
|
||||
// Handles regular files & symlinks (and finds out what is what by looking at
|
||||
// `out.Mode`).
|
||||
func (volume *Volume) translateSize(dirfd int, cName string, st *syscall.Stat_t) uint64 {
|
||||
var size uint64
|
||||
if isRegular(st.Mode) {
|
||||
size = volume.contentEnc.CipherSizeToPlainSize(uint64(st.Size))
|
||||
} else if isSymlink(st.Mode) {
|
||||
target := volume.readlink(dirfd, cName)
|
||||
size = uint64(len(target))
|
||||
}
|
||||
return size
|
||||
}
|
44
info.go
44
info.go
@ -1,44 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
// info pretty-prints the contents of the config file at "filename" for human
|
||||
// consumption, stripping out sensitive data.
|
||||
// This is called when you pass the "-info" option.
|
||||
func info(filename string) {
|
||||
// Read from disk
|
||||
js, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("Reading config file failed: %v", err)
|
||||
os.Exit(exitcodes.LoadConf)
|
||||
}
|
||||
// Unmarshal
|
||||
var cf configfile.ConfFile
|
||||
err = json.Unmarshal(js, &cf)
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("Failed to unmarshal config file")
|
||||
os.Exit(exitcodes.LoadConf)
|
||||
}
|
||||
if cf.Version != contentenc.CurrentVersion {
|
||||
tlog.Fatal.Printf("Unsupported on-disk format %d", cf.Version)
|
||||
os.Exit(exitcodes.LoadConf)
|
||||
}
|
||||
// Pretty-print
|
||||
fmt.Printf("Creator: %s\n", cf.Creator)
|
||||
fmt.Printf("FeatureFlags: %s\n", strings.Join(cf.FeatureFlags, " "))
|
||||
fmt.Printf("EncryptedKey: %dB\n", len(cf.EncryptedKey))
|
||||
s := cf.ScryptObject
|
||||
fmt.Printf("ScryptObject: Salt=%dB N=%d R=%d P=%d KeyLen=%d\n",
|
||||
len(s.Salt), s.N, s.R, s.P, s.KeyLen)
|
||||
}
|
134
init_dir.go
134
init_dir.go
@ -1,134 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/fido2"
|
||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
// isEmptyDir checks if "dir" exists and is an empty directory.
|
||||
// Returns an *os.PathError if Stat() on the path fails.
|
||||
func isEmptyDir(dir string) error {
|
||||
err := isDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entries, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("directory %s not empty", dir)
|
||||
}
|
||||
|
||||
// isDir checks if "dir" exists and is a directory.
|
||||
func isDir(dir string) error {
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return fmt.Errorf("%s is not a directory", dir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initDir handles "gocryptfs -init". It prepares a directory for use as a
|
||||
// gocryptfs storage directory.
|
||||
// In forward mode, this means creating the gocryptfs.conf and gocryptfs.diriv
|
||||
// files in an empty directory.
|
||||
// In reverse mode, we create .gocryptfs.reverse.conf and the directory does
|
||||
// not need to be empty.
|
||||
func initDir(args *argContainer) {
|
||||
var err error
|
||||
if args.reverse {
|
||||
_, err = os.Stat(args.config)
|
||||
if err == nil {
|
||||
tlog.Fatal.Printf("Config file %q already exists", args.config)
|
||||
os.Exit(exitcodes.Init)
|
||||
}
|
||||
} else {
|
||||
err = isEmptyDir(args.cipherdir)
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("Invalid cipherdir: %v", err)
|
||||
os.Exit(exitcodes.CipherDir)
|
||||
}
|
||||
}
|
||||
// Choose password for config file
|
||||
if args.extpass.Empty() && args.fido2 == "" {
|
||||
tlog.Info.Printf("Choose a password for protecting your files.")
|
||||
}
|
||||
{
|
||||
var password []byte
|
||||
var fido2CredentialID, fido2HmacSalt []byte
|
||||
if args.fido2 != "" {
|
||||
fido2CredentialID = fido2.Register(args.fido2, filepath.Base(args.cipherdir))
|
||||
fido2HmacSalt = cryptocore.RandBytes(32)
|
||||
password = fido2.Secret(args.fido2, fido2CredentialID, fido2HmacSalt)
|
||||
} else {
|
||||
// normal password entry
|
||||
password = readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
||||
fido2CredentialID = nil
|
||||
fido2HmacSalt = nil
|
||||
}
|
||||
creator := tlog.ProgramName + " " + GitVersion
|
||||
err = configfile.Create(args.config, password, args.plaintextnames,
|
||||
args.scryptn, creator, args.aessiv, args.devrandom, fido2CredentialID, fido2HmacSalt)
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.WriteConf)
|
||||
}
|
||||
for i := range password {
|
||||
password[i] = 0
|
||||
}
|
||||
// password runs out of scope here
|
||||
}
|
||||
// Forward mode with filename encryption enabled needs a gocryptfs.diriv file
|
||||
// in the root dir
|
||||
if !args.plaintextnames && !args.reverse {
|
||||
// Open cipherdir (following symlinks)
|
||||
dirfd, err := syscall.Open(args.cipherdir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
if err == nil {
|
||||
err = nametransform.WriteDirIVAt(dirfd)
|
||||
syscall.Close(dirfd)
|
||||
}
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.Init)
|
||||
}
|
||||
}
|
||||
mountArgs := ""
|
||||
fsName := "gocryptfs"
|
||||
if args.reverse {
|
||||
mountArgs = " -reverse"
|
||||
fsName = "gocryptfs-reverse"
|
||||
}
|
||||
tlog.Info.Printf(tlog.ColorGreen+"The %s filesystem has been created successfully."+tlog.ColorReset,
|
||||
fsName)
|
||||
wd, _ := os.Getwd()
|
||||
friendlyPath, _ := filepath.Rel(wd, args.cipherdir)
|
||||
if strings.HasPrefix(friendlyPath, "../") {
|
||||
// A relative path that starts with "../" is pretty unfriendly, just
|
||||
// keep the absolute path.
|
||||
friendlyPath = args.cipherdir
|
||||
}
|
||||
if strings.Contains(friendlyPath, " ") {
|
||||
friendlyPath = "\"" + friendlyPath + "\""
|
||||
}
|
||||
tlog.Info.Printf(tlog.ColorGrey+"You can now mount it using: %s%s %s MOUNTPOINT"+tlog.ColorReset,
|
||||
tlog.ProgramName, mountArgs, friendlyPath)
|
||||
}
|
@ -12,10 +12,9 @@ import (
|
||||
|
||||
"os"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
"../contentenc"
|
||||
"../cryptocore"
|
||||
"../exitcodes"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -113,11 +112,10 @@ func Create(filename string, password []byte, plaintextNames bool,
|
||||
} else {
|
||||
key = cryptocore.RandBytes(cryptocore.KeyLen)
|
||||
}
|
||||
tlog.PrintMasterkeyReminder(key)
|
||||
// Encrypt it using the password
|
||||
// This sets ScryptObject and EncryptedKey
|
||||
// Note: this looks at the FeatureFlags, so call it AFTER setting them.
|
||||
cf.EncryptKey(key, password, logN)
|
||||
cf.EncryptKey(key, password, logN, false)
|
||||
for i := range key {
|
||||
key[i] = 0
|
||||
}
|
||||
@ -147,7 +145,7 @@ func LoadAndDecrypt(filename string, password []byte) ([]byte, *ConfFile, error)
|
||||
}
|
||||
|
||||
// Decrypt the masterkey using the password
|
||||
key, err := cf.DecryptMasterKey(password)
|
||||
key, _, err := cf.DecryptMasterKey(password, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -172,7 +170,6 @@ func Load(filename string) (*ConfFile, error) {
|
||||
// Unmarshal
|
||||
err = json.Unmarshal(js, &cf)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("Failed to unmarshal config file")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -202,17 +199,6 @@ func Load(filename string) (*ConfFile, error) {
|
||||
}
|
||||
}
|
||||
if deprecatedFs {
|
||||
fmt.Fprintf(os.Stderr, tlog.ColorYellow+`
|
||||
The filesystem was created by gocryptfs v0.6 or earlier. This version of
|
||||
gocryptfs can no longer mount the filesystem.
|
||||
Please download gocryptfs v0.11 and upgrade your filesystem,
|
||||
see https://github.com/rfjakob/gocryptfs/wiki/Upgrading for instructions.
|
||||
|
||||
If you have trouble upgrading, join the discussion at
|
||||
https://github.com/rfjakob/gocryptfs/issues/29 .
|
||||
|
||||
`+tlog.ColorReset)
|
||||
|
||||
return nil, exitcodes.NewErr("Deprecated filesystem", exitcodes.DeprecatedFS)
|
||||
}
|
||||
|
||||
@ -222,38 +208,38 @@ func Load(filename string) (*ConfFile, error) {
|
||||
|
||||
// DecryptMasterKey decrypts the masterkey stored in cf.EncryptedKey using
|
||||
// password.
|
||||
func (cf *ConfFile) DecryptMasterKey(password []byte) (masterkey []byte, err error) {
|
||||
func (cf *ConfFile) DecryptMasterKey(password []byte, giveHash bool) (masterkey, scryptHash []byte, err error) {
|
||||
// Generate derived key from password
|
||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||
scryptHash = cf.ScryptObject.DeriveKey(password)
|
||||
|
||||
// Unlock master key using password-based key
|
||||
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
||||
ce := getKeyEncrypter(scryptHash, useHKDF)
|
||||
|
||||
tlog.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password
|
||||
masterkey, err = ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
tlog.Warn.Enabled = true
|
||||
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
if !giveHash {
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
scryptHash = nil
|
||||
}
|
||||
scryptHash = nil
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("failed to unlock master key: %s", err.Error())
|
||||
return nil, exitcodes.NewErr("Password incorrect.", exitcodes.PasswordIncorrect)
|
||||
return nil, nil, exitcodes.NewErr("Password incorrect.", exitcodes.PasswordIncorrect)
|
||||
}
|
||||
return masterkey, nil
|
||||
|
||||
return masterkey, scryptHash, nil
|
||||
}
|
||||
|
||||
// EncryptKey - encrypt "key" using an scrypt hash generated from "password"
|
||||
// and store it in cf.EncryptedKey.
|
||||
// Uses scrypt with cost parameter logN and stores the scrypt parameters in
|
||||
// cf.ScryptObject.
|
||||
func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int) {
|
||||
func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int, giveHash bool) []byte {
|
||||
// Generate scrypt-derived key from password
|
||||
cf.ScryptObject = NewScryptKDF(logN)
|
||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||
@ -263,13 +249,45 @@ func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int) {
|
||||
ce := getKeyEncrypter(scryptHash, useHKDF)
|
||||
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
||||
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
if !giveHash {
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
scryptHash = nil
|
||||
}
|
||||
scryptHash = nil
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
|
||||
return scryptHash
|
||||
}
|
||||
|
||||
// DroidFS function to allow masterkey to be decrypted directely using the scrypt hash and return it if requested
|
||||
func (cf *ConfFile) GetMasterkey(password, givenScryptHash, returnedScryptHashBuff []byte) []byte {
|
||||
var masterkey []byte
|
||||
var err error
|
||||
var scryptHash []byte
|
||||
if len(givenScryptHash) > 0 { //decrypt with hash
|
||||
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
||||
ce := getKeyEncrypter(givenScryptHash, useHKDF)
|
||||
masterkey, err = ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
if err == nil {
|
||||
return masterkey
|
||||
}
|
||||
} else { //decrypt with password
|
||||
masterkey, scryptHash, err = cf.DecryptMasterKey(password, len(returnedScryptHashBuff) > 0)
|
||||
//copy and wipe scryptHash
|
||||
for i := range scryptHash {
|
||||
returnedScryptHashBuff[i] = scryptHash[i]
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
if err == nil {
|
||||
return masterkey
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteFile - write out config in JSON format to file "filename.tmp"
|
||||
@ -296,7 +314,6 @@ func (cf *ConfFile) WriteFile() error {
|
||||
if err != nil {
|
||||
// This can happen on network drives: FRITZ.NAS mounted on MacOS returns
|
||||
// "operation not supported": https://github.com/rfjakob/gocryptfs/issues/390
|
||||
tlog.Warn.Printf("Warning: fsync failed: %v", err)
|
||||
// Try sync instead
|
||||
syscall.Sync()
|
||||
}
|
||||
|
@ -1,147 +0,0 @@
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
var testPw = []byte("test")
|
||||
|
||||
func TestLoadV1(t *testing.T) {
|
||||
_, _, err := LoadAndDecrypt("config_test/v1.conf", testPw)
|
||||
if err == nil {
|
||||
t.Errorf("Outdated v1 config file must fail to load but it didn't")
|
||||
} else if testing.Verbose() {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load a known-good config file and verify that it takes at least 100ms
|
||||
// (brute-force protection)
|
||||
func TestLoadV2(t *testing.T) {
|
||||
t1 := time.Now()
|
||||
|
||||
_, _, err := LoadAndDecrypt("config_test/v2.conf", testPw)
|
||||
if err != nil {
|
||||
t.Errorf("Could not load v2 config file: %v", err)
|
||||
}
|
||||
|
||||
elapsed := time.Since(t1)
|
||||
if elapsed < 100*time.Millisecond {
|
||||
t.Errorf("scrypt calculation runs too fast: %d ms", elapsed/time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadV2PwdError(t *testing.T) {
|
||||
if !testing.Verbose() {
|
||||
tlog.Warn.Enabled = false
|
||||
}
|
||||
_, _, err := LoadAndDecrypt("config_test/v2.conf", []byte("wrongpassword"))
|
||||
if err == nil {
|
||||
t.Errorf("Loading with wrong password must fail but it didn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadV2Feature(t *testing.T) {
|
||||
_, _, err := LoadAndDecrypt("config_test/PlaintextNames.conf", testPw)
|
||||
if err != nil {
|
||||
t.Errorf("Could not load v2 PlaintextNames config file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadV2StrangeFeature(t *testing.T) {
|
||||
_, _, err := LoadAndDecrypt("config_test/StrangeFeature.conf", testPw)
|
||||
if err == nil {
|
||||
t.Errorf("Loading unknown feature must fail but it didn't")
|
||||
} else if testing.Verbose() {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateConfDefault(t *testing.T) {
|
||||
err := Create("config_test/tmp.conf", testPw, false, 10, "test", false, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, c, err := LoadAndDecrypt("config_test/tmp.conf", testPw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Check that all expected feature flags are set
|
||||
want := []flagIota{
|
||||
FlagGCMIV128, FlagDirIV, FlagEMENames, FlagLongNames,
|
||||
FlagRaw64, FlagHKDF,
|
||||
}
|
||||
for _, f := range want {
|
||||
if !c.IsFeatureFlagSet(f) {
|
||||
t.Errorf("Feature flag %q should be set but is not", knownFlags[f])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateConfDevRandom(t *testing.T) {
|
||||
err := Create("config_test/tmp.conf", testPw, false, 10, "test", false, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateConfPlaintextnames(t *testing.T) {
|
||||
err := Create("config_test/tmp.conf", testPw, true, 10, "test", false, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, c, err := LoadAndDecrypt("config_test/tmp.conf", testPw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Check that all expected feature flags are set
|
||||
want := []flagIota{
|
||||
FlagGCMIV128, FlagHKDF,
|
||||
}
|
||||
for _, f := range want {
|
||||
if !c.IsFeatureFlagSet(f) {
|
||||
t.Errorf("Feature flag %q should be set but is not", knownFlags[f])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse mode uses AESSIV
|
||||
func TestCreateConfFileAESSIV(t *testing.T) {
|
||||
err := Create("config_test/tmp.conf", testPw, false, 10, "test", true, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, c, err := LoadAndDecrypt("config_test/tmp.conf", testPw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !c.IsFeatureFlagSet(FlagAESSIV) {
|
||||
t.Error("AESSIV flag should be set but is not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFeatureFlagKnown(t *testing.T) {
|
||||
// Test a few hardcoded values
|
||||
testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames", "AESSIV"}
|
||||
// And also everything in knownFlags (yes, it is likely that we end up with
|
||||
// some duplicates. Does not matter.)
|
||||
for _, f := range knownFlags {
|
||||
testKnownFlags = append(testKnownFlags, f)
|
||||
}
|
||||
|
||||
var cf ConfFile
|
||||
for _, f := range testKnownFlags {
|
||||
if !cf.isFeatureFlagKnown(f) {
|
||||
t.Errorf("flag %q should be known", f)
|
||||
}
|
||||
}
|
||||
|
||||
f := "StrangeFeatureFlag"
|
||||
if cf.isFeatureFlagKnown(f) {
|
||||
t.Errorf("flag %q should be NOT known", f)
|
||||
}
|
||||
}
|
1
internal/configfile/config_test/.gitignore
vendored
1
internal/configfile/config_test/.gitignore
vendored
@ -1 +0,0 @@
|
||||
tmp.conf
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"Creator": "gocryptfs v0.11-13-g96750a7-dirty",
|
||||
"EncryptedKey": "pH6/kgPFrwkuFW/HDN/0UzwC8hLJCMm/upyEnsR1pVTfSJLL/JxfBaVCRyuZhc/S7h2PrxVSMO1xzLrk",
|
||||
"ScryptObject": {
|
||||
"Salt": "Hq0BqXXeMGVGfdYE1Y/qcW+pvxJBJymRAVgPUxQiZ8Y=",
|
||||
"N": 1024,
|
||||
"R": 8,
|
||||
"P": 1,
|
||||
"KeyLen": 32
|
||||
},
|
||||
"Version": 2,
|
||||
"FeatureFlags": [
|
||||
"GCMIV128",
|
||||
"PlaintextNames"
|
||||
]
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"Creator": "gocryptfs v0.11-13-g96750a7-dirty",
|
||||
"EncryptedKey": "mfN2FITcsLE+8QlpTb3r/D5rAAqEX5mJQuU655tcdwAotUwHkrIdYiKa2BjoocctQC0grwqPyuWxB7SH",
|
||||
"ScryptObject": {
|
||||
"Salt": "9G2knR016guT/AJqOKemjusYhqg+mI177Dz6a5RS7ts=",
|
||||
"N": 1024,
|
||||
"R": 8,
|
||||
"P": 1,
|
||||
"KeyLen": 32
|
||||
},
|
||||
"Version": 2,
|
||||
"FeatureFlags": [
|
||||
"GCMIV128",
|
||||
"DirIV",
|
||||
"EMENames",
|
||||
"LongNames",
|
||||
"StrangeFeatureFlag"
|
||||
]
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"EncryptedKey": "t6YAvFQJvbv46c93bHQ5IZnvNz80DA9cohGoSPL/2M257LuIigow6jbr8b9HhnbDqHTCcz7aKkMDzneF",
|
||||
"ScryptObject": {
|
||||
"Salt": "yT4yQmmRmVNx2P0tJrUswk5SQzZaL6Z8kUteAoNJkXM=",
|
||||
"N": 65536,
|
||||
"R": 8,
|
||||
"P": 1,
|
||||
"KeyLen": 32
|
||||
},
|
||||
"Version": 1
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
{
|
||||
"Creator": "gocryptfs v0.11-13-g96750a7-dirty",
|
||||
"EncryptedKey": "zY06x2JS0Fi7bkZ/3DppjmWZOI6xTjQ/Bf4Nru1rUzHml+stkAtvcoHT8PpHN4eKqL73ymQ86MmdYz+1",
|
||||
"ScryptObject": {
|
||||
"Salt": "U5MbvyTmwhEkLqe6XxlrONzPZEf2GLFZCAIixz2Kal0=",
|
||||
"N": 65536,
|
||||
"R": 8,
|
||||
"P": 1,
|
||||
"KeyLen": 32
|
||||
},
|
||||
"Version": 2,
|
||||
"FeatureFlags": [
|
||||
"GCMIV128",
|
||||
"DirIV",
|
||||
"EMENames",
|
||||
"LongNames"
|
||||
]
|
||||
}
|
@ -7,9 +7,8 @@ import (
|
||||
|
||||
"golang.org/x/crypto/scrypt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
"../cryptocore"
|
||||
"../exitcodes"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -84,23 +83,18 @@ func (s *ScryptKDF) LogN() int {
|
||||
func (s *ScryptKDF) validateParams() {
|
||||
minN := 1 << scryptMinLogN
|
||||
if s.N < minN {
|
||||
tlog.Fatal.Println("Fatal: scryptn below 10 is too low to make sense")
|
||||
os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if s.R < scryptMinR {
|
||||
tlog.Fatal.Printf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
|
||||
os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if s.P < scryptMinP {
|
||||
tlog.Fatal.Printf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
|
||||
os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if len(s.Salt) < scryptMinSaltLen {
|
||||
tlog.Fatal.Printf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
|
||||
os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
if s.KeyLen < cryptocore.KeyLen {
|
||||
tlog.Fatal.Printf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
|
||||
os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
package configfile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
Results on a 2.7GHz Pentium G630:
|
||||
|
||||
gocryptfs/cryptfs$ go test -bench=.
|
||||
PASS
|
||||
BenchmarkScrypt10-2 300 6021435 ns/op ... 6ms
|
||||
BenchmarkScrypt11-2 100 11861460 ns/op
|
||||
BenchmarkScrypt12-2 100 23420822 ns/op
|
||||
BenchmarkScrypt13-2 30 47666518 ns/op
|
||||
BenchmarkScrypt14-2 20 92561590 ns/op ... 92ms
|
||||
BenchmarkScrypt15-2 10 183971593 ns/op
|
||||
BenchmarkScrypt16-2 3 368506365 ns/op
|
||||
BenchmarkScrypt17-2 2 755502608 ns/op ... 755ms
|
||||
ok github.com/rfjakob/gocryptfs/cryptfs 18.772s
|
||||
*/
|
||||
|
||||
func benchmarkScryptN(n int, b *testing.B) {
|
||||
kdf := NewScryptKDF(n)
|
||||
for i := 0; i < b.N; i++ {
|
||||
kdf.DeriveKey(testPw)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkScrypt10(b *testing.B) {
|
||||
benchmarkScryptN(10, b)
|
||||
}
|
||||
|
||||
func BenchmarkScrypt11(b *testing.B) {
|
||||
benchmarkScryptN(11, b)
|
||||
}
|
||||
|
||||
func BenchmarkScrypt12(b *testing.B) {
|
||||
benchmarkScryptN(12, b)
|
||||
}
|
||||
|
||||
func BenchmarkScrypt13(b *testing.B) {
|
||||
benchmarkScryptN(13, b)
|
||||
}
|
||||
|
||||
func BenchmarkScrypt14(b *testing.B) {
|
||||
benchmarkScryptN(14, b)
|
||||
}
|
||||
|
||||
func BenchmarkScrypt15(b *testing.B) {
|
||||
benchmarkScryptN(15, b)
|
||||
}
|
||||
|
||||
func BenchmarkScrypt16(b *testing.B) {
|
||||
benchmarkScryptN(16, b)
|
||||
}
|
||||
|
||||
func BenchmarkScrypt17(b *testing.B) {
|
||||
benchmarkScryptN(17, b)
|
||||
}
|
@ -4,23 +4,22 @@ package contentenc
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
"../cryptocore"
|
||||
"../stupidgcm"
|
||||
)
|
||||
|
||||
// NonceMode determines how nonces are created.
|
||||
type NonceMode int
|
||||
|
||||
const (
|
||||
//value from FUSE doc
|
||||
MAX_KERNEL_WRITE = 128 * 1024
|
||||
|
||||
// DefaultBS is the default plaintext block size
|
||||
DefaultBS = 4096
|
||||
// DefaultIVBits is the default length of IV, in bits.
|
||||
@ -73,17 +72,17 @@ type ContentEnc struct {
|
||||
|
||||
// New returns an initialized ContentEnc instance.
|
||||
func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc {
|
||||
if fuse.MAX_KERNEL_WRITE%plainBS != 0 {
|
||||
log.Panicf("unaligned MAX_KERNEL_WRITE=%d", fuse.MAX_KERNEL_WRITE)
|
||||
if MAX_KERNEL_WRITE%plainBS != 0 {
|
||||
log.Panicf("unaligned MAX_KERNEL_WRITE=%d", MAX_KERNEL_WRITE)
|
||||
}
|
||||
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
|
||||
// Take IV and GHASH overhead into account.
|
||||
cReqSize := int(fuse.MAX_KERNEL_WRITE / plainBS * cipherBS)
|
||||
cReqSize := int(MAX_KERNEL_WRITE / plainBS * cipherBS)
|
||||
// Unaligned reads (happens during fsck, could also happen with O_DIRECT?)
|
||||
// touch one additional ciphertext and plaintext block. Reserve space for the
|
||||
// extra block.
|
||||
cReqSize += int(cipherBS)
|
||||
pReqSize := fuse.MAX_KERNEL_WRITE + int(plainBS)
|
||||
pReqSize := MAX_KERNEL_WRITE + int(plainBS)
|
||||
c := &ContentEnc{
|
||||
cryptoCore: cc,
|
||||
plainBS: plainBS,
|
||||
@ -120,9 +119,7 @@ func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, file
|
||||
var pBlock []byte
|
||||
pBlock, err = be.DecryptBlock(cBlock, blockNo, fileID)
|
||||
if err != nil {
|
||||
if be.forceDecode && err == stupidgcm.ErrAuth {
|
||||
tlog.Warn.Printf("DecryptBlocks: authentication failure in block #%d, overridden by forcedecode", firstBlockNo)
|
||||
} else {
|
||||
if !(be.forceDecode && err == stupidgcm.ErrAuth) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -163,12 +160,10 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
||||
|
||||
// All-zero block?
|
||||
if bytes.Equal(ciphertext, be.allZeroBlock) {
|
||||
tlog.Debug.Printf("DecryptBlock: file hole encountered")
|
||||
return make([]byte, be.plainBS), nil
|
||||
}
|
||||
|
||||
if len(ciphertext) < be.cryptoCore.IVLen {
|
||||
tlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext))
|
||||
return nil, errors.New("Block is too short")
|
||||
}
|
||||
|
||||
@ -180,7 +175,6 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
||||
// http://www.spinics.net/lists/kernel/msg2370127.html
|
||||
return nil, errors.New("all-zero nonce")
|
||||
}
|
||||
ciphertextOrig := ciphertext
|
||||
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
||||
|
||||
// Decrypt
|
||||
@ -190,8 +184,6 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
||||
plaintext, err := be.cryptoCore.AEADCipher.Open(plaintext, nonce, ciphertext, aData)
|
||||
|
||||
if err != nil {
|
||||
tlog.Debug.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig))
|
||||
tlog.Debug.Println(hex.Dump(ciphertextOrig))
|
||||
if be.forceDecode && err == stupidgcm.ErrAuth {
|
||||
return plaintext, err
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
package contentenc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
)
|
||||
|
||||
type testRange struct {
|
||||
offset uint64
|
||||
length uint64
|
||||
}
|
||||
|
||||
func TestSplitRange(t *testing.T) {
|
||||
var ranges []testRange
|
||||
|
||||
ranges = append(ranges, testRange{0, 70000},
|
||||
testRange{0, 10},
|
||||
testRange{234, 6511},
|
||||
testRange{65444, 54},
|
||||
testRange{0, 1024 * 1024},
|
||||
testRange{0, 65536},
|
||||
testRange{6654, 8945})
|
||||
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
|
||||
f := New(cc, DefaultBS, false)
|
||||
|
||||
for _, r := range ranges {
|
||||
parts := f.ExplodePlainRange(r.offset, r.length)
|
||||
var lastBlockNo uint64 = 1 << 63
|
||||
for _, p := range parts {
|
||||
if p.BlockNo == lastBlockNo {
|
||||
t.Errorf("Duplicate block number %d", p.BlockNo)
|
||||
}
|
||||
lastBlockNo = p.BlockNo
|
||||
if p.Length > DefaultBS || p.Skip >= DefaultBS {
|
||||
t.Errorf("Test fail: n=%d, length=%d, offset=%d\n", p.BlockNo, p.Length, p.Skip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCiphertextRange(t *testing.T) {
|
||||
var ranges []testRange
|
||||
|
||||
ranges = append(ranges, testRange{0, 70000},
|
||||
testRange{0, 10},
|
||||
testRange{234, 6511},
|
||||
testRange{65444, 54},
|
||||
testRange{6654, 8945})
|
||||
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
|
||||
f := New(cc, DefaultBS, false)
|
||||
|
||||
for _, r := range ranges {
|
||||
|
||||
blocks := f.ExplodePlainRange(r.offset, r.length)
|
||||
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
|
||||
skipBytes := blocks[0].Skip
|
||||
|
||||
if alignedLength < r.length {
|
||||
t.Errorf("alignedLength=%d is smaller than length=%d", alignedLength, r.length)
|
||||
}
|
||||
if (alignedOffset-HeaderLen)%f.cipherBS != 0 {
|
||||
t.Errorf("alignedOffset=%d is not aligned", alignedOffset)
|
||||
}
|
||||
if r.offset%f.plainBS != 0 && skipBytes == 0 {
|
||||
t.Errorf("skipBytes=0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockNo(t *testing.T) {
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
|
||||
f := New(cc, DefaultBS, false)
|
||||
|
||||
b := f.CipherOffToBlockNo(788)
|
||||
if b != 0 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.CipherOffToBlockNo(HeaderLen + f.cipherBS)
|
||||
if b != 1 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.PlainOffToBlockNo(788)
|
||||
if b != 0 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.PlainOffToBlockNo(f.plainBS)
|
||||
if b != 1 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"../cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -2,8 +2,6 @@ package contentenc
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
// Contentenc methods that translate offsets between ciphertext and plaintext
|
||||
@ -44,12 +42,10 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
|
||||
if cipherSize == HeaderLen {
|
||||
// This can happen between createHeader() and Write() and is harmless.
|
||||
tlog.Debug.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize)
|
||||
return 0
|
||||
}
|
||||
|
||||
if cipherSize < HeaderLen {
|
||||
tlog.Warn.Printf("cipherSize %d < header size %d: corrupt file\n", cipherSize, HeaderLen)
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -58,7 +54,6 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
lastBlockSize := (cipherSize - HeaderLen) % be.cipherBS
|
||||
if lastBlockSize > 0 && lastBlockSize <= be.BlockOverhead() {
|
||||
tmp := cipherSize - lastBlockSize + be.BlockOverhead() + 1
|
||||
tlog.Warn.Printf("cipherSize %d: incomplete last block (%d bytes), padding to %d bytes", cipherSize, lastBlockSize, tmp)
|
||||
cipherSize = tmp
|
||||
}
|
||||
|
||||
@ -69,7 +64,6 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
||||
overhead := be.BlockOverhead()*blockCount + HeaderLen
|
||||
|
||||
if overhead > cipherSize {
|
||||
tlog.Warn.Printf("cipherSize %d < overhead %d: corrupt file\n", cipherSize, overhead)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
package contentenc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
)
|
||||
|
||||
// TestSizeToSize tests CipherSizeToPlainSize and PlainSizeToCipherSize
|
||||
func TestSizeToSize(t *testing.T) {
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
|
||||
ce := New(cc, DefaultBS, false)
|
||||
|
||||
const rangeMax = 10000
|
||||
|
||||
// y values in this order:
|
||||
// 0 ... CipherSizeToPlainSize
|
||||
// 1 ... PlainSizeToCipherSize
|
||||
// 2 ... PlainOffToCipherOff
|
||||
var yTable [rangeMax][3]uint64
|
||||
|
||||
// Calculate values
|
||||
for x := range yTable {
|
||||
yTable[x][0] = ce.CipherSizeToPlainSize(uint64(x))
|
||||
yTable[x][1] = ce.PlainSizeToCipherSize(uint64(x))
|
||||
yTable[x][2] = ce.PlainOffToCipherOff(uint64(x))
|
||||
}
|
||||
|
||||
// Print data table
|
||||
fmt.Print("x\tCipherSizeToPlainSize\tPlainSizeToCipherSize\tPlainOffToCipherOff\n")
|
||||
for x := range yTable {
|
||||
if x > 1 && x < rangeMax-1 {
|
||||
// If the point before has value-1 and the point after has value+1,
|
||||
// it is not interesting. Don't print it out.
|
||||
interesting := false
|
||||
for i := 0; i <= 2; i++ {
|
||||
if yTable[x-1][i]+1 != yTable[x][i] && yTable[x][i]+1 != yTable[x+1][i]+1 {
|
||||
interesting = true
|
||||
}
|
||||
// Monotonicity check
|
||||
if yTable[x][i] < yTable[x-1][i] {
|
||||
t.Errorf("column %d is non-monotonic!", i)
|
||||
}
|
||||
}
|
||||
if !interesting {
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Printf("%d\t%d\t%d\t%d\n", x, yTable[x][0], yTable[x][1], yTable[x][2])
|
||||
}
|
||||
}
|
@ -12,9 +12,8 @@ import (
|
||||
|
||||
"github.com/rfjakob/eme"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/siv_aead"
|
||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
"../siv_aead"
|
||||
"../stupidgcm"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -157,13 +156,10 @@ type wiper interface {
|
||||
func (c *CryptoCore) Wipe() {
|
||||
be := c.AEADBackend
|
||||
if be == BackendOpenSSL || be == BackendAESSIV {
|
||||
tlog.Debug.Printf("CryptoCore.Wipe: Wiping AEADBackend %d key", be)
|
||||
// We don't use "x, ok :=" because we *want* to crash loudly if the
|
||||
// type assertion fails.
|
||||
w := c.AEADCipher.(wiper)
|
||||
w.Wipe()
|
||||
} else {
|
||||
tlog.Debug.Printf("CryptoCore.Wipe: Only nil'ing stdlib refs")
|
||||
}
|
||||
// We have no access to the keys (or key-equivalents) stored inside the
|
||||
// Go stdlib. Best we can is to nil the references and force a GC.
|
||||
|
@ -1,41 +0,0 @@
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
||||
)
|
||||
|
||||
// "New" should accept at least these param combinations
|
||||
func TestCryptoCoreNew(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
for _, useHKDF := range []bool{true, false} {
|
||||
c := New(key, BackendGoGCM, 96, useHKDF, false)
|
||||
if c.IVLen != 12 {
|
||||
t.Fail()
|
||||
}
|
||||
c = New(key, BackendGoGCM, 128, useHKDF, false)
|
||||
if c.IVLen != 16 {
|
||||
t.Fail()
|
||||
}
|
||||
if stupidgcm.BuiltWithoutOpenssl {
|
||||
continue
|
||||
}
|
||||
c = New(key, BackendOpenSSL, 128, useHKDF, false)
|
||||
if c.IVLen != 16 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "New" should panic on any key not 32 bytes long
|
||||
func TestNewPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("The code did not panic")
|
||||
}
|
||||
}()
|
||||
|
||||
key := make([]byte, 16)
|
||||
New(key, BackendOpenSSL, 128, true, false)
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type hkdfTestCase struct {
|
||||
masterkey []byte
|
||||
info string
|
||||
out []byte
|
||||
}
|
||||
|
||||
// TestHkdfDerive verifies that we get the expected values from hkdfDerive. They
|
||||
// must not change because this would change the on-disk format.
|
||||
func TestHkdfDerive(t *testing.T) {
|
||||
master0 := bytes.Repeat([]byte{0x00}, 32)
|
||||
master1 := bytes.Repeat([]byte{0x01}, 32)
|
||||
out1, _ := hex.DecodeString("9ba3cddd48c6339c6e56ebe85f0281d6e9051be4104176e65cb0f8a6f77ae6b4")
|
||||
out2, _ := hex.DecodeString("e8a2499f48700b954f31de732efd04abce822f5c948e7fbc0896607be0d36d12")
|
||||
out3, _ := hex.DecodeString("9137f2e67a842484137f3c458f357f204c30d7458f94f432fa989be96854a649")
|
||||
out4, _ := hex.DecodeString("0bfa5da7d9724d4753269940d36898e2c0f3717c0fee86ada58b5fd6c08cc26c")
|
||||
|
||||
testCases := []hkdfTestCase{
|
||||
{master0, "EME filename encryption", out1},
|
||||
{master0, hkdfInfoEMENames, out1},
|
||||
{master1, "EME filename encryption", out2},
|
||||
{master1, hkdfInfoEMENames, out2},
|
||||
{master1, "AES-GCM file content encryption", out3},
|
||||
{master1, hkdfInfoGCMContent, out3},
|
||||
{master1, "AES-SIV file content encryption", out4},
|
||||
{master1, hkdfInfoSIVContent, out4},
|
||||
}
|
||||
|
||||
for i, v := range testCases {
|
||||
out := hkdfDerive(v.masterkey, v.info, 32)
|
||||
if !bytes.Equal(out, v.out) {
|
||||
want := hex.EncodeToString(v.out)
|
||||
have := hex.EncodeToString(out)
|
||||
t.Errorf("testcase %d error:\n"+
|
||||
"want=%s\n"+
|
||||
"have=%s", i, want, have)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRandPrefetch hammers the randPrefetcher with 100 goroutines and verifies
|
||||
// that the result is incompressible
|
||||
func TestRandPrefetch(t *testing.T) {
|
||||
runtime.GOMAXPROCS(10)
|
||||
p := 100
|
||||
l := 200
|
||||
vec := make([][]byte, p)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < p; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
var tmp []byte
|
||||
for x := 0; x < l; x++ {
|
||||
tmp = append(tmp, randPrefetcher.read(l)...)
|
||||
}
|
||||
vec[i] = tmp
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
var b bytes.Buffer
|
||||
fw, _ := flate.NewWriter(&b, flate.BestCompression)
|
||||
for _, v := range vec {
|
||||
fw.Write(v)
|
||||
}
|
||||
fw.Close()
|
||||
if b.Len() < p*l*l {
|
||||
t.Errorf("random data should be incompressible, but: in=%d compressed=%d\n", p*l*l, b.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRandPrefetch(b *testing.B) {
|
||||
// 16-byte nonces are default since gocryptfs v0.7
|
||||
b.SetBytes(16)
|
||||
for i := 0; i < b.N; i++ {
|
||||
randPrefetcher.read(16)
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// +build go1.7
|
||||
|
||||
// ^^^^^^^^^^^^ we use the "sub-benchmark" feature that was added in Go 1.7
|
||||
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
The troughput we get from /dev/urandom / getentropy depends a lot on the used
|
||||
block size. Results on my Pentium G630 running Linux 4.11:
|
||||
|
||||
BenchmarkRandSize/16-2 3000000 571 ns/op 27.98 MB/s
|
||||
BenchmarkRandSize/32-2 3000000 585 ns/op 54.66 MB/s
|
||||
BenchmarkRandSize/64-2 2000000 860 ns/op 74.36 MB/s
|
||||
BenchmarkRandSize/128-2 1000000 1197 ns/op 106.90 MB/s
|
||||
BenchmarkRandSize/256-2 1000000 1867 ns/op 137.06 MB/s
|
||||
BenchmarkRandSize/512-2 500000 3187 ns/op 160.61 MB/s
|
||||
BenchmarkRandSize/1024-2 200000 5888 ns/op 173.91 MB/s
|
||||
BenchmarkRandSize/2048-2 100000 11554 ns/op 177.25 MB/s
|
||||
BenchmarkRandSize/4096-2 100000 22523 ns/op 181.86 MB/s
|
||||
BenchmarkRandSize/8192-2 30000 43111 ns/op 190.02 MB/s
|
||||
|
||||
Results are similar when testing with dd, so this is not due to Go allocation
|
||||
overhead: dd if=/dev/urandom bs=16 count=100000 of=/dev/null
|
||||
*/
|
||||
func BenchmarkUrandomBlocksize(b *testing.B) {
|
||||
for s := 16; s <= 8192; s *= 2 {
|
||||
title := fmt.Sprintf("%d", s)
|
||||
b.Run(title, func(b *testing.B) {
|
||||
b.SetBytes(int64(s))
|
||||
for i := 0; i < b.N; i++ {
|
||||
RandBytes(s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
// Package ctlsocksrv implements the control socket interface that can be
|
||||
// activated by passing "-ctlsock" on the command line.
|
||||
package ctlsocksrv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/ctlsock"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
// Interface should be implemented by fusefrontend[_reverse]
|
||||
type Interface interface {
|
||||
EncryptPath(string) (string, error)
|
||||
DecryptPath(string) (string, error)
|
||||
}
|
||||
|
||||
type ctlSockHandler struct {
|
||||
fs Interface
|
||||
socket *net.UnixListener
|
||||
}
|
||||
|
||||
// Serve serves incoming connections on "sock". This call blocks so you
|
||||
// probably want to run it in a new goroutine.
|
||||
func Serve(sock net.Listener, fs Interface) {
|
||||
handler := ctlSockHandler{
|
||||
fs: fs,
|
||||
socket: sock.(*net.UnixListener),
|
||||
}
|
||||
handler.acceptLoop()
|
||||
}
|
||||
|
||||
func (ch *ctlSockHandler) acceptLoop() {
|
||||
for {
|
||||
conn, err := ch.socket.Accept()
|
||||
if err != nil {
|
||||
// This can trigger on program exit with "use of closed network connection".
|
||||
// Special-casing this is hard due to https://github.com/golang/go/issues/4373
|
||||
// so just don't use tlog.Warn to not cause panics in the tests.
|
||||
tlog.Info.Printf("ctlsock: Accept error: %v", err)
|
||||
break
|
||||
}
|
||||
go ch.handleConnection(conn.(*net.UnixConn))
|
||||
}
|
||||
}
|
||||
|
||||
// ReadBufSize is the size of the request read buffer.
|
||||
// The longest possible path is 4096 bytes on Linux and 1024 on Mac OS X so
|
||||
// 5000 bytes should be enough to hold the whole JSON request. This
|
||||
// assumes that the path does not contain too many characters that had to be
|
||||
// be escaped in JSON (for example, a null byte blows up to "\u0000").
|
||||
// We abort the connection if the request is bigger than this.
|
||||
const ReadBufSize = 5000
|
||||
|
||||
// handleConnection reads and parses JSON requests from "conn"
|
||||
func (ch *ctlSockHandler) handleConnection(conn *net.UnixConn) {
|
||||
buf := make([]byte, ReadBufSize)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err == io.EOF {
|
||||
conn.Close()
|
||||
return
|
||||
} else if err != nil {
|
||||
tlog.Warn.Printf("ctlsock: Read error: %#v", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if n == ReadBufSize {
|
||||
tlog.Warn.Printf("ctlsock: request too big (max = %d bytes)", ReadBufSize-1)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
data := buf[:n]
|
||||
var in ctlsock.RequestStruct
|
||||
err = json.Unmarshal(data, &in)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("ctlsock: JSON Unmarshal error: %#v", err)
|
||||
err = errors.New("JSON Unmarshal error: " + err.Error())
|
||||
sendResponse(conn, err, "", "")
|
||||
continue
|
||||
}
|
||||
ch.handleRequest(&in, conn)
|
||||
}
|
||||
}
|
||||
|
||||
// handleRequest handles an already-unmarshaled JSON request
|
||||
func (ch *ctlSockHandler) handleRequest(in *ctlsock.RequestStruct, conn *net.UnixConn) {
|
||||
var err error
|
||||
var inPath, outPath, clean, warnText string
|
||||
// You cannot perform both decryption and encryption in one request
|
||||
if in.DecryptPath != "" && in.EncryptPath != "" {
|
||||
err = errors.New("Ambiguous")
|
||||
sendResponse(conn, err, "", "")
|
||||
return
|
||||
}
|
||||
// Neither encryption nor encryption has been requested, makes no sense
|
||||
if in.DecryptPath == "" && in.EncryptPath == "" {
|
||||
err = errors.New("Empty input")
|
||||
sendResponse(conn, err, "", "")
|
||||
return
|
||||
}
|
||||
// Canonicalize input path
|
||||
if in.EncryptPath != "" {
|
||||
inPath = in.EncryptPath
|
||||
} else {
|
||||
inPath = in.DecryptPath
|
||||
}
|
||||
clean = SanitizePath(inPath)
|
||||
// Warn if a non-canonical path was passed
|
||||
if inPath != clean {
|
||||
warnText = fmt.Sprintf("Non-canonical input path '%s' has been interpreted as '%s'.", inPath, clean)
|
||||
}
|
||||
// Error out if the canonical path is now empty
|
||||
if clean == "" {
|
||||
err = errors.New("Empty input after canonicalization")
|
||||
sendResponse(conn, err, "", warnText)
|
||||
return
|
||||
}
|
||||
// Actual encrypt or decrypt operation
|
||||
if in.EncryptPath != "" {
|
||||
outPath, err = ch.fs.EncryptPath(clean)
|
||||
} else {
|
||||
outPath, err = ch.fs.DecryptPath(clean)
|
||||
}
|
||||
sendResponse(conn, err, outPath, warnText)
|
||||
}
|
||||
|
||||
// sendResponse sends a JSON response message
|
||||
func sendResponse(conn *net.UnixConn, err error, result string, warnText string) {
|
||||
msg := ctlsock.ResponseStruct{
|
||||
Result: result,
|
||||
WarnText: warnText,
|
||||
}
|
||||
if err != nil {
|
||||
msg.ErrText = err.Error()
|
||||
msg.ErrNo = -1
|
||||
// Try to extract the actual error number
|
||||
if pe, ok := err.(*os.PathError); ok {
|
||||
if se, ok := pe.Err.(syscall.Errno); ok {
|
||||
msg.ErrNo = int32(se)
|
||||
}
|
||||
} else if err == syscall.ENOENT {
|
||||
msg.ErrNo = int32(syscall.ENOENT)
|
||||
}
|
||||
}
|
||||
jsonMsg, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("ctlsock: Marshal failed: %v", err)
|
||||
return
|
||||
}
|
||||
// For convenience for the user, add a newline at the end.
|
||||
jsonMsg = append(jsonMsg, '\n')
|
||||
_, err = conn.Write(jsonMsg)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("ctlsock: Write failed: %v", err)
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package ctlsocksrv
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SanitizePath adapts filepath.Clean for FUSE paths.
|
||||
// 1) Leading slash(es) are dropped
|
||||
// 2) It returns "" instead of "."
|
||||
// 3) If the cleaned path points above CWD (start with ".."), an empty string
|
||||
// is returned
|
||||
// See the TestSanitizePath testcases for examples.
|
||||
func SanitizePath(path string) string {
|
||||
// (1)
|
||||
for len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return ""
|
||||
}
|
||||
clean := filepath.Clean(path)
|
||||
// (2)
|
||||
if clean == "." {
|
||||
return ""
|
||||
}
|
||||
// (3)
|
||||
if clean == ".." || strings.HasPrefix(clean, "../") {
|
||||
return ""
|
||||
}
|
||||
return clean
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package ctlsocksrv
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSanitizePath(t *testing.T) {
|
||||
testCases := [][]string{
|
||||
{"", ""},
|
||||
{".", ""},
|
||||
{"/", ""},
|
||||
{"foo", "foo"},
|
||||
{"/foo", "foo"},
|
||||
{"foo/", "foo"},
|
||||
{"/foo/", "foo"},
|
||||
{"/foo/./foo", "foo/foo"},
|
||||
{"./", ""},
|
||||
{"..", ""},
|
||||
{"foo/../..", ""},
|
||||
{"foo/../../aaaaaa", ""},
|
||||
{"/foo/../../aaaaaa", ""},
|
||||
{"/////", ""},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
res := SanitizePath(tc[0])
|
||||
if res != tc[1] {
|
||||
t.Errorf("%q: got %q, want %q", tc[0], res, tc[1])
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user