Genesis patch
This commit is contained in:
parent
9046d6d922
commit
847d4fa781
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,22 +1,10 @@
|
|||||||
# the gocryptfs executable
|
/build
|
||||||
/gocryptfs
|
/include
|
||||||
|
/lib
|
||||||
|
/openssl*
|
||||||
|
|
||||||
# temporary files created by the tests
|
# temporary files created by the tests
|
||||||
/tmp
|
/tmp
|
||||||
|
|
||||||
# binary releases and signatiures
|
|
||||||
/*.tar.gz
|
|
||||||
/*.asc
|
|
||||||
|
|
||||||
# Binaries created for cpu profiling
|
# Binaries created for cpu profiling
|
||||||
*.test
|
*.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/)
|
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 ?
|
||||||
[![Build Status](https://travis-ci.org/rfjakob/gocryptfs.svg?branch=master)](https://travis-ci.org/rfjakob/gocryptfs)
|
- Allow the use of gocryptfs in embedded devices where FUSE is not available (such as Android)
|
||||||
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
|
- Reduce attack surface by restricting volumes access to only one process rather than one user
|
||||||
[![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)
|
|
||||||
|
|
||||||
An encrypted overlay filesystem written in Go.
|
## Warning !
|
||||||
Official website: https://nuetzlich.net/gocryptfs ([markdown source](https://github.com/rfjakob/gocryptfs-website/blob/master/docs/index.md)).
|
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 !
|
||||||
|
|
||||||
![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
|
|
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"
|
"os"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
"../contentenc"
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
"../cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
"../exitcodes"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -113,11 +112,10 @@ func Create(filename string, password []byte, plaintextNames bool,
|
|||||||
} else {
|
} else {
|
||||||
key = cryptocore.RandBytes(cryptocore.KeyLen)
|
key = cryptocore.RandBytes(cryptocore.KeyLen)
|
||||||
}
|
}
|
||||||
tlog.PrintMasterkeyReminder(key)
|
|
||||||
// Encrypt it using the password
|
// Encrypt it using the password
|
||||||
// This sets ScryptObject and EncryptedKey
|
// This sets ScryptObject and EncryptedKey
|
||||||
// Note: this looks at the FeatureFlags, so call it AFTER setting them.
|
// 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 {
|
for i := range key {
|
||||||
key[i] = 0
|
key[i] = 0
|
||||||
}
|
}
|
||||||
@ -147,7 +145,7 @@ func LoadAndDecrypt(filename string, password []byte) ([]byte, *ConfFile, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the masterkey using the password
|
// Decrypt the masterkey using the password
|
||||||
key, err := cf.DecryptMasterKey(password)
|
key, _, err := cf.DecryptMasterKey(password, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -172,7 +170,6 @@ func Load(filename string) (*ConfFile, error) {
|
|||||||
// Unmarshal
|
// Unmarshal
|
||||||
err = json.Unmarshal(js, &cf)
|
err = json.Unmarshal(js, &cf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("Failed to unmarshal config file")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,17 +199,6 @@ func Load(filename string) (*ConfFile, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if deprecatedFs {
|
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)
|
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
|
// DecryptMasterKey decrypts the masterkey stored in cf.EncryptedKey using
|
||||||
// password.
|
// 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
|
// Generate derived key from password
|
||||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
scryptHash = cf.ScryptObject.DeriveKey(password)
|
||||||
|
|
||||||
// Unlock master key using password-based key
|
// Unlock master key using password-based key
|
||||||
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
||||||
ce := getKeyEncrypter(scryptHash, useHKDF)
|
ce := getKeyEncrypter(scryptHash, useHKDF)
|
||||||
|
|
||||||
tlog.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password
|
|
||||||
masterkey, err = ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
masterkey, err = ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||||
tlog.Warn.Enabled = true
|
|
||||||
|
|
||||||
// Purge scrypt-derived key
|
if !giveHash {
|
||||||
for i := range scryptHash {
|
// Purge scrypt-derived key
|
||||||
scryptHash[i] = 0
|
for i := range scryptHash {
|
||||||
|
scryptHash[i] = 0
|
||||||
|
}
|
||||||
|
scryptHash = nil
|
||||||
}
|
}
|
||||||
scryptHash = nil
|
|
||||||
ce.Wipe()
|
ce.Wipe()
|
||||||
ce = nil
|
ce = nil
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("failed to unlock master key: %s", err.Error())
|
return nil, nil, exitcodes.NewErr("Password incorrect.", exitcodes.PasswordIncorrect)
|
||||||
return nil, exitcodes.NewErr("Password incorrect.", exitcodes.PasswordIncorrect)
|
|
||||||
}
|
}
|
||||||
return masterkey, nil
|
|
||||||
|
return masterkey, scryptHash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptKey - encrypt "key" using an scrypt hash generated from "password"
|
// EncryptKey - encrypt "key" using an scrypt hash generated from "password"
|
||||||
// and store it in cf.EncryptedKey.
|
// and store it in cf.EncryptedKey.
|
||||||
// Uses scrypt with cost parameter logN and stores the scrypt parameters in
|
// Uses scrypt with cost parameter logN and stores the scrypt parameters in
|
||||||
// cf.ScryptObject.
|
// 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
|
// Generate scrypt-derived key from password
|
||||||
cf.ScryptObject = NewScryptKDF(logN)
|
cf.ScryptObject = NewScryptKDF(logN)
|
||||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||||
@ -263,13 +249,45 @@ func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int) {
|
|||||||
ce := getKeyEncrypter(scryptHash, useHKDF)
|
ce := getKeyEncrypter(scryptHash, useHKDF)
|
||||||
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
||||||
|
|
||||||
// Purge scrypt-derived key
|
if !giveHash {
|
||||||
for i := range scryptHash {
|
// Purge scrypt-derived key
|
||||||
scryptHash[i] = 0
|
for i := range scryptHash {
|
||||||
|
scryptHash[i] = 0
|
||||||
|
}
|
||||||
|
scryptHash = nil
|
||||||
}
|
}
|
||||||
scryptHash = nil
|
|
||||||
ce.Wipe()
|
ce.Wipe()
|
||||||
ce = nil
|
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"
|
// WriteFile - write out config in JSON format to file "filename.tmp"
|
||||||
@ -296,7 +314,6 @@ func (cf *ConfFile) WriteFile() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// This can happen on network drives: FRITZ.NAS mounted on MacOS returns
|
// This can happen on network drives: FRITZ.NAS mounted on MacOS returns
|
||||||
// "operation not supported": https://github.com/rfjakob/gocryptfs/issues/390
|
// "operation not supported": https://github.com/rfjakob/gocryptfs/issues/390
|
||||||
tlog.Warn.Printf("Warning: fsync failed: %v", err)
|
|
||||||
// Try sync instead
|
// Try sync instead
|
||||||
syscall.Sync()
|
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"
|
"golang.org/x/crypto/scrypt"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
"../cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
"../exitcodes"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -84,23 +83,18 @@ func (s *ScryptKDF) LogN() int {
|
|||||||
func (s *ScryptKDF) validateParams() {
|
func (s *ScryptKDF) validateParams() {
|
||||||
minN := 1 << scryptMinLogN
|
minN := 1 << scryptMinLogN
|
||||||
if s.N < minN {
|
if s.N < minN {
|
||||||
tlog.Fatal.Println("Fatal: scryptn below 10 is too low to make sense")
|
|
||||||
os.Exit(exitcodes.ScryptParams)
|
os.Exit(exitcodes.ScryptParams)
|
||||||
}
|
}
|
||||||
if s.R < scryptMinR {
|
if s.R < scryptMinR {
|
||||||
tlog.Fatal.Printf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
|
|
||||||
os.Exit(exitcodes.ScryptParams)
|
os.Exit(exitcodes.ScryptParams)
|
||||||
}
|
}
|
||||||
if s.P < scryptMinP {
|
if s.P < scryptMinP {
|
||||||
tlog.Fatal.Printf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
|
|
||||||
os.Exit(exitcodes.ScryptParams)
|
os.Exit(exitcodes.ScryptParams)
|
||||||
}
|
}
|
||||||
if len(s.Salt) < scryptMinSaltLen {
|
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)
|
os.Exit(exitcodes.ScryptParams)
|
||||||
}
|
}
|
||||||
if s.KeyLen < cryptocore.KeyLen {
|
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)
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hanwen/go-fuse/v2/fuse"
|
"../cryptocore"
|
||||||
|
"../stupidgcm"
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NonceMode determines how nonces are created.
|
// NonceMode determines how nonces are created.
|
||||||
type NonceMode int
|
type NonceMode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
//value from FUSE doc
|
||||||
|
MAX_KERNEL_WRITE = 128 * 1024
|
||||||
|
|
||||||
// DefaultBS is the default plaintext block size
|
// DefaultBS is the default plaintext block size
|
||||||
DefaultBS = 4096
|
DefaultBS = 4096
|
||||||
// DefaultIVBits is the default length of IV, in bits.
|
// DefaultIVBits is the default length of IV, in bits.
|
||||||
@ -73,17 +72,17 @@ type ContentEnc struct {
|
|||||||
|
|
||||||
// New returns an initialized ContentEnc instance.
|
// New returns an initialized ContentEnc instance.
|
||||||
func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc {
|
func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc {
|
||||||
if fuse.MAX_KERNEL_WRITE%plainBS != 0 {
|
if MAX_KERNEL_WRITE%plainBS != 0 {
|
||||||
log.Panicf("unaligned MAX_KERNEL_WRITE=%d", fuse.MAX_KERNEL_WRITE)
|
log.Panicf("unaligned MAX_KERNEL_WRITE=%d", MAX_KERNEL_WRITE)
|
||||||
}
|
}
|
||||||
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
|
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
|
||||||
// Take IV and GHASH overhead into account.
|
// 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?)
|
// Unaligned reads (happens during fsck, could also happen with O_DIRECT?)
|
||||||
// touch one additional ciphertext and plaintext block. Reserve space for the
|
// touch one additional ciphertext and plaintext block. Reserve space for the
|
||||||
// extra block.
|
// extra block.
|
||||||
cReqSize += int(cipherBS)
|
cReqSize += int(cipherBS)
|
||||||
pReqSize := fuse.MAX_KERNEL_WRITE + int(plainBS)
|
pReqSize := MAX_KERNEL_WRITE + int(plainBS)
|
||||||
c := &ContentEnc{
|
c := &ContentEnc{
|
||||||
cryptoCore: cc,
|
cryptoCore: cc,
|
||||||
plainBS: plainBS,
|
plainBS: plainBS,
|
||||||
@ -120,9 +119,7 @@ func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, file
|
|||||||
var pBlock []byte
|
var pBlock []byte
|
||||||
pBlock, err = be.DecryptBlock(cBlock, blockNo, fileID)
|
pBlock, err = be.DecryptBlock(cBlock, blockNo, fileID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if be.forceDecode && err == stupidgcm.ErrAuth {
|
if !(be.forceDecode && err == stupidgcm.ErrAuth) {
|
||||||
tlog.Warn.Printf("DecryptBlocks: authentication failure in block #%d, overridden by forcedecode", firstBlockNo)
|
|
||||||
} else {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,12 +160,10 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
|||||||
|
|
||||||
// All-zero block?
|
// All-zero block?
|
||||||
if bytes.Equal(ciphertext, be.allZeroBlock) {
|
if bytes.Equal(ciphertext, be.allZeroBlock) {
|
||||||
tlog.Debug.Printf("DecryptBlock: file hole encountered")
|
|
||||||
return make([]byte, be.plainBS), nil
|
return make([]byte, be.plainBS), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ciphertext) < be.cryptoCore.IVLen {
|
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")
|
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
|
// http://www.spinics.net/lists/kernel/msg2370127.html
|
||||||
return nil, errors.New("all-zero nonce")
|
return nil, errors.New("all-zero nonce")
|
||||||
}
|
}
|
||||||
ciphertextOrig := ciphertext
|
|
||||||
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
||||||
|
|
||||||
// Decrypt
|
// 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)
|
plaintext, err := be.cryptoCore.AEADCipher.Open(plaintext, nonce, ciphertext, aData)
|
||||||
|
|
||||||
if err != nil {
|
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 {
|
if be.forceDecode && err == stupidgcm.ErrAuth {
|
||||||
return plaintext, err
|
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"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
"../cryptocore"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -2,8 +2,6 @@ package contentenc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Contentenc methods that translate offsets between ciphertext and plaintext
|
// Contentenc methods that translate offsets between ciphertext and plaintext
|
||||||
@ -44,12 +42,10 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
|||||||
|
|
||||||
if cipherSize == HeaderLen {
|
if cipherSize == HeaderLen {
|
||||||
// This can happen between createHeader() and Write() and is harmless.
|
// This can happen between createHeader() and Write() and is harmless.
|
||||||
tlog.Debug.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize)
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if cipherSize < HeaderLen {
|
if cipherSize < HeaderLen {
|
||||||
tlog.Warn.Printf("cipherSize %d < header size %d: corrupt file\n", cipherSize, HeaderLen)
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +54,6 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
|||||||
lastBlockSize := (cipherSize - HeaderLen) % be.cipherBS
|
lastBlockSize := (cipherSize - HeaderLen) % be.cipherBS
|
||||||
if lastBlockSize > 0 && lastBlockSize <= be.BlockOverhead() {
|
if lastBlockSize > 0 && lastBlockSize <= be.BlockOverhead() {
|
||||||
tmp := cipherSize - lastBlockSize + be.BlockOverhead() + 1
|
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
|
cipherSize = tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +64,6 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
|||||||
overhead := be.BlockOverhead()*blockCount + HeaderLen
|
overhead := be.BlockOverhead()*blockCount + HeaderLen
|
||||||
|
|
||||||
if overhead > cipherSize {
|
if overhead > cipherSize {
|
||||||
tlog.Warn.Printf("cipherSize %d < overhead %d: corrupt file\n", cipherSize, overhead)
|
|
||||||
return 0
|
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/eme"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/siv_aead"
|
"../siv_aead"
|
||||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
"../stupidgcm"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -157,13 +156,10 @@ type wiper interface {
|
|||||||
func (c *CryptoCore) Wipe() {
|
func (c *CryptoCore) Wipe() {
|
||||||
be := c.AEADBackend
|
be := c.AEADBackend
|
||||||
if be == BackendOpenSSL || be == BackendAESSIV {
|
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
|
// We don't use "x, ok :=" because we *want* to crash loudly if the
|
||||||
// type assertion fails.
|
// type assertion fails.
|
||||||
w := c.AEADCipher.(wiper)
|
w := c.AEADCipher.(wiper)
|
||||||
w.Wipe()
|
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
|
// 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.
|
// 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