Compare commits
24 Commits
master
...
libgocrypt
Author | SHA1 | Date |
---|---|---|
Matéo Duparc | 4f32853ae5 | |
Matéo Duparc | ab3e788676 | |
Matéo Duparc | 6308adf8e5 | |
Matéo Duparc | a238cc392f | |
Matéo Duparc | 79f9a10e35 | |
Matéo Duparc | f3b722fdff | |
Matéo Duparc | 27232cbdb7 | |
Matéo Duparc | e6e4c201db | |
Matéo Duparc | 7afeb9f3a4 | |
Matéo Duparc | 9e98192442 | |
Matéo Duparc | 985d852343 | |
Matéo Duparc | b2ddf58e89 | |
Matéo Duparc | 71eb2bdf7c | |
Matéo Duparc | 89966b1aae | |
Matéo Duparc | 1da2407a61 | |
Matéo Duparc | b232bb7826 | |
Matéo Duparc | d6e75be376 | |
Matéo Duparc | f86a1aa6a8 | |
Matéo Duparc | 1973153602 | |
Matéo Duparc | bd5d53f50e | |
Matéo Duparc | f0e45c7b7e | |
Matéo Duparc | 47c6caf2d5 | |
Matéo Duparc | 39af24d4e6 | |
Matéo Duparc | 847d4fa781 |
|
@ -1,51 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 12 * * *' # Every day noon UTC
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
go:
|
||||
- "1.13.x" # Ubuntu 20.04 LTS "focal"
|
||||
- "1.15.x" # Debian 11 "Bullseye"
|
||||
- "1.18.x" # Ubuntu 22.04 LTS "jammy"
|
||||
- "oldstable" # 2nd-latest Golang upstream stable
|
||||
- "stable" # Latest Go upstream stable
|
||||
# Don't cancel everything when one Go version fails
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Install Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
# Looks like Github Actions leaks fds to child processes
|
||||
# https://github.com/actions/runner/issues/1188
|
||||
- run: ls -l /proc/self/fd
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Make "git describe" work
|
||||
|
||||
# CI platform specific setup steps happen here
|
||||
- run: sudo apt-get install -qq fuse3 libssl-dev
|
||||
|
||||
# Fix "/usr/bin/fusermount: option allow_other only allowed if 'user_allow_other' is set in /etc/fuse.conf"
|
||||
- run: echo user_allow_other | sudo tee -a /etc/fuse.conf
|
||||
|
||||
# Build & upload static binary
|
||||
- run: ./build-without-openssl.bash
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gocryptfs static binary (Go ${{ matrix.go }})
|
||||
path: gocryptfs
|
||||
|
||||
# Actual test steps are in the Makefile
|
||||
- run: make ci
|
|
@ -1,22 +1,10 @@
|
|||
# the gocryptfs executable
|
||||
/gocryptfs
|
||||
/build
|
||||
/include
|
||||
/lib
|
||||
/openssl*
|
||||
|
||||
# temporary files created by the tests
|
||||
/tmp
|
||||
|
||||
# binary releases and signatiures
|
||||
/*.tar.gz
|
||||
/*.asc
|
||||
|
||||
# Binaries created for cpu profiling
|
||||
*.test
|
||||
|
||||
# Rendered manpage
|
||||
gocryptfs.1
|
||||
|
||||
# Dependencies copied by "dep"
|
||||
/vendor
|
||||
/_vendor-*
|
||||
|
||||
# Source tarball version. Should never be committed to git.
|
||||
/VERSION
|
||||
|
|
|
@ -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
|
||||
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,761 +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 AES-GCM but is
|
||||
secure with deterministic nonces as used in "-reverse" mode.
|
||||
|
||||
Run `gocryptfs -speed` to find out if and how much slower.
|
||||
|
||||
#### -deterministic-names
|
||||
Disable file name randomisation and creation of `gocryptfs.diriv` files.
|
||||
This can prevent sync conflicts conflicts when synchronising files, but
|
||||
leaks information about identical file names across directories
|
||||
("Identical names leak" in https://nuetzlich.net/gocryptfs/comparison/#file-names ).
|
||||
|
||||
The resulting `gocryptfs.conf` has "DirIV" missing from "FeatureFlags".
|
||||
|
||||
#### -devrandom
|
||||
Obsolete and ignored on gocryptfs v2.2 and later.
|
||||
|
||||
See https://github.com/rfjakob/gocryptfs/commit/f3c777d5eaa682d878c638192311e52f9c204294
|
||||
and https://github.com/rfjakob/gocryptfs/issues/596 for background info.
|
||||
|
||||
#### -hkdf
|
||||
Use HKDF to derive separate keys for content and name encryption from
|
||||
the master key. Default true.
|
||||
|
||||
#### -longnamemax
|
||||
|
||||
integer value, allowed range 62...255
|
||||
|
||||
Hash file names that (in encrypted form) exceed this length. The default
|
||||
is 255, which aligns with the usual name length limit on Linux and
|
||||
provides best performance.
|
||||
|
||||
However, online storage may impose lower limits on file name and/or
|
||||
path length. In this case, setting -longnamemax to a lower value
|
||||
can be helpful.
|
||||
|
||||
The lower the value, the more extra `.name` files
|
||||
must be created, which slows down directory listings.
|
||||
|
||||
Values below 62 are not allowed as then the hashed name
|
||||
would be longer than the original name.
|
||||
|
||||
Example:
|
||||
|
||||
-longnamemax 100
|
||||
|
||||
#### -plaintextnames
|
||||
Do not encrypt file names and symlink targets.
|
||||
|
||||
#### -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".
|
||||
|
||||
#### -xchacha
|
||||
Use XChaCha20-Poly1305 file content encryption. This should be much faster
|
||||
than AES-GCM on CPUs that lack AES acceleration.
|
||||
|
||||
Run `gocryptfs -speed` to find out if and how much faster.
|
||||
|
||||
MOUNT OPTIONS
|
||||
=============
|
||||
|
||||
Available options for mounting are listed below. Usually, you don't need any.
|
||||
Defaults are fine.
|
||||
|
||||
#### -acl
|
||||
Enable ACL enforcement. When you want to use ACLs, you must enable this
|
||||
option.
|
||||
|
||||
#### -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).
|
||||
|
||||
#### -badname string
|
||||
When gocryptfs encounters a "bad" file name (cannot be decrypted or decrypts
|
||||
to garbage), a warning is logged and the file is hidden from the
|
||||
plaintext view.
|
||||
|
||||
With the `-badname` option, you can select "bad" file names that should
|
||||
still be shown in the plaintext view instead of hiding them. Bad files
|
||||
will get ` GOCRYPTFS_BAD_NAME` appended to their name.
|
||||
|
||||
Glob pattern. Can be passed multiple times for multiple patterns.
|
||||
|
||||
Examples:
|
||||
|
||||
Dropbox sync conflicts:
|
||||
|
||||
-badname '*conflicted copy*'
|
||||
|
||||
Syncthing sync conflicts:
|
||||
|
||||
-badname '*.sync-conflict*'
|
||||
|
||||
Show all invalid filenames:
|
||||
|
||||
-badname '*'
|
||||
|
||||
#### -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 that excludes the directories "Music" and "Movies" from the root
|
||||
directory:
|
||||
|
||||
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 GITIGNORE-PATTERN, -exclude-wildcard GITIGNORE-PATTERN
|
||||
Only for reverse mode: exclude paths from the encrypted view in gitignore(5) syntax,
|
||||
wildcards supported. Pass multiple times for multiple patterns.
|
||||
|
||||
Example to exclude all `.mp3` files in any directory:
|
||||
|
||||
gocryptfs -reverse -exclude-wildcard '*.mp3' /home/user /mnt/user.encrypted
|
||||
|
||||
Example to to exclude everything but the directory 'important' in the root dir:
|
||||
|
||||
gocryptfs -reverse -exclude-wildcard '*' -exclude-wildcard '!/important' /home/user /mnt/user.encrypted
|
||||
|
||||
See also `-exclude-from` and the [EXCLUDING FILES](#excluding-files) section.
|
||||
|
||||
#### -exclude-from FILE
|
||||
Only for reverse mode: reads gitignore patterns
|
||||
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
|
||||
Obsolete and ignored on gocryptfs v2.2 and later.
|
||||
|
||||
See https://github.com/rfjakob/gocryptfs/commit/d023cd6c95fcbc6b5056ba1f425d2ac3df4abc5a
|
||||
for what it was and why it was dropped.
|
||||
|
||||
#### -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" enables volume-based trash
|
||||
if you have `.Trashes` folder in the root of your volume (might need to be manually created)
|
||||
note, though, that "local" is marked as "experimental" in [osxfuse](https://github.com/osxfuse/osxfuse/wiki/Mount-options#local);
|
||||
"noapplexattr", "noappledouble" may also 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 that are longer than 175 bytes in extra files (default true).
|
||||
|
||||
This flag is only useful when recovering very old gocryptfs filesystems (gocryptfs v0.8 and earlier)
|
||||
using "-masterkey". It is ignored (stays at the default) otherwise.
|
||||
|
||||
#### -nodev
|
||||
See `-dev, -nodev`.
|
||||
|
||||
#### -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`.
|
||||
|
||||
#### -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.
|
||||
|
||||
#### -notifypid int
|
||||
Send USR1 to the specified process after successful mount. This is
|
||||
used internally for daemonization.
|
||||
|
||||
#### -one-file-system
|
||||
Don't cross filesystem boundaries (like rsync's `--one-file-system`).
|
||||
Mountpoints will appear as empty directories.
|
||||
|
||||
Only applicable to reverse mode.
|
||||
|
||||
Limitation: Mounted single files (yes this is possible) are NOT hidden.
|
||||
|
||||
#### -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 specify 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.
|
||||
|
||||
BUG: In `-extpass -X`, the `-X` will be interpreted as `--X`. Please use
|
||||
`-extpass=-X` to prevent that. See **Dash duplication** in the **BUGS** section
|
||||
for details.
|
||||
|
||||
#### -fido2 DEVICE_PATH
|
||||
Use a FIDO2 token to initialize and unlock the filesystem.
|
||||
Use "fido2-token -L" to obtain the FIDO2 token device path.
|
||||
For linux, "fido2-tools" package is needed.
|
||||
|
||||
Applies to: all actions that ask for a password.
|
||||
|
||||
#### -masterkey string
|
||||
Use an 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.
|
||||
|
||||
#### -scryptn int
|
||||
gocryptfs uses *scrypt* for hashing the password when mounting,
|
||||
which protects from brute-force attacks.
|
||||
|
||||
`-scryptn` controls the *scrypt* cost parameter "N" expressed as scryptn=log2(N).
|
||||
Possible values are `-scryptn=10` to `-scryptn=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.
|
||||
|
||||
The memory usage for *scrypt* during mounting is as follows:
|
||||
|
||||
scryptn Memory Usage
|
||||
======= ============
|
||||
10 1 MiB
|
||||
11 2
|
||||
12 4
|
||||
13 8
|
||||
14 16
|
||||
15 32
|
||||
16 64
|
||||
17 128
|
||||
18 256
|
||||
19 512
|
||||
20 1 GiB
|
||||
21 2
|
||||
22 4
|
||||
23 8
|
||||
24 16
|
||||
25 32
|
||||
26 64
|
||||
27 128
|
||||
28 256
|
||||
|
||||
Applies to: `-init`, `-passwd`
|
||||
|
||||
See also: the benchmarks in the gocryptfs source code in internal/configfile.
|
||||
|
||||
#### -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. Wildcards are NOT supported.
|
||||
This option is kept for compatibility with the behavior up to version 1.6.x.
|
||||
New users should use `-exclude-wildcard` instead.
|
||||
|
||||
`-exclude-wildcard` uses gitignore syntax and 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 gitignore
|
||||
patterns from a file. As with `-exclude-wildcard`, 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
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
=====================
|
||||
|
||||
### NO_COLOR
|
||||
|
||||
If `NO_COLOR` is set (regardless of value), colored output is disabled (see https://no-color.org/).
|
||||
|
||||
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
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
### Dash duplication
|
||||
|
||||
gocryptfs v2.1 switched to the `pflag` library for command-line parsing
|
||||
to support flags and positional arguments in any order. To stay compatible
|
||||
with single-dash long options like `-extpass`, an ugly hack was added:
|
||||
The command line is preprocessed, and all single-dash options are converted to
|
||||
double-dash.
|
||||
|
||||
Unfortunately, this means that in
|
||||
|
||||
gocryptfs -extpass myapp -extpass -X
|
||||
|
||||
gocryptfs transforms the `-X` to `--X`, and it will call `myapp --X` as the extpass program.
|
||||
|
||||
Please use
|
||||
|
||||
gocryptfs -extpass myapp -extpass=-X
|
||||
|
||||
to work around this bug.
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
mount(2) fuse(8) fallocate(2) encfs(1) gitignore(5)
|
|
@ -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,63 +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
|
||||
|
||||
Data block, XChaCha20-Poly1305 (enabled via `-init -xchacha`)
|
||||
|
||||
24 bytes nonce
|
||||
1-4096 bytes encrypted data
|
||||
16 bytes Poly1305 tag
|
||||
|
||||
Full block overhead (AES-GCM and AES-SIV mode) = 32/4096 = 1/128 = 0.78125 %
|
||||
|
||||
Full block overhead (XChaCha20-Poly1305 mode) = 40/4096 = \~1 %
|
||||
|
||||
Example: 1-byte file, AES-GCM and AES-SIV mode
|
||||
----------------------------------------------
|
||||
|
||||
Header 18 bytes
|
||||
Data block 33 bytes
|
||||
|
||||
Total: 51 bytes
|
||||
|
||||
Example: 5000-byte file, , AES-GCM and AES-SIV mode
|
||||
---------------------------------------------------
|
||||
|
||||
Header 18 bytes
|
||||
Data block 4128 bytes
|
||||
Data block 936 bytes
|
||||
|
||||
Total: 5082 bytes
|
||||
|
||||
Example: 1-byte file, XChaCha20-Poly1305 mode
|
||||
----------------------------------------------
|
||||
|
||||
Header 18 bytes
|
||||
Data block 41 bytes
|
||||
|
||||
Total: 59 bytes
|
||||
|
||||
Example: 5000-byte file, XChaCha20-Poly1305 mode
|
||||
----------------------------------------------
|
||||
|
||||
Header 18 bytes
|
||||
Data block 4136 bytes
|
||||
Data block 944 bytes
|
||||
|
||||
Total: 5098 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,87 +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
|
||||
v2.0.1-28-g49507ea 471 991 8.6 4.5 1.7 2.2
|
||||
|
||||
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
|
61
Makefile
61
Makefile
|
@ -1,61 +0,0 @@
|
|||
.phony: build
|
||||
build:
|
||||
./build.bash
|
||||
./Documentation/MANPAGE-render.bash
|
||||
|
||||
.phony: test
|
||||
test:
|
||||
./test.bash
|
||||
|
||||
.phony: root_test
|
||||
root_test:
|
||||
./build.bash
|
||||
|
||||
# Need to use TMPDIR=/var/tmp as TestOverlay fails on tmpfs.
|
||||
cd tests/root_test && go test -c && sudo TMPDIR=/var/tmp ./root_test.test -test.v
|
||||
|
||||
cd tests/cli && go test -c && sudo ./cli.test -test.v -test.run=TestDirectMount
|
||||
|
||||
.phony: format
|
||||
format:
|
||||
go fmt ./...
|
||||
|
||||
# Keep in sync with uninstall!
|
||||
.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
|
||||
|
||||
.phony: uninstall
|
||||
uninstall:
|
||||
rm -f "$(DESTDIR)/usr/bin/gocryptfs"
|
||||
rm -f "$(DESTDIR)/usr/bin/gocryptfs-xray"
|
||||
rm -f "$(DESTDIR)/usr/share/man/man1/gocryptfs.1"
|
||||
rm -f "$(DESTDIR)/usr/share/man/man1/gocryptfs-xray.1"
|
||||
rm -f "$(DESTDIR)/usr/share/licenses/gocryptfs/LICENSE"
|
||||
|
||||
.phony: ci
|
||||
ci:
|
||||
uname -a ; go version ; openssl version
|
||||
df -Th / /tmp /var/tmp
|
||||
|
||||
./build-without-openssl.bash
|
||||
./build.bash
|
||||
./test.bash
|
||||
make root_test
|
||||
./crossbuild.bash
|
||||
|
||||
echo "Rebuild with locked dependencies"
|
||||
# Download dependencies to "vendor" directory
|
||||
go mod vendor
|
||||
# Delete global cache
|
||||
go clean -modcache
|
||||
# GOPROXY=off makes sure we fail instead of making network requests
|
||||
# (we should not need any!)
|
||||
# "-mod=vendor" is required for Go 1.13
|
||||
GOPROXY=off ./build.bash -mod=vendor
|
||||
# Delete "vendor" dir
|
||||
rm -R vendor
|
787
README.md
787
README.md
|
@ -1,783 +1,6 @@
|
|||
[![gocryptfs](Documentation/gocryptfs-logo.png)](https://nuetzlich.net/gocryptfs/)
|
||||
[![CI](https://github.com/rfjakob/gocryptfs/actions/workflows/ci.yml/badge.svg)](https://github.com/rfjakob/gocryptfs/actions/workflows/ci.yml)
|
||||
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/rfjakob/gocryptfs)](https://goreportcard.com/report/github.com/rfjakob/gocryptfs)
|
||||
[![Latest release](https://img.shields.io/github/release/rfjakob/gocryptfs.svg)](https://github.com/rfjakob/gocryptfs/releases)
|
||||
[![Homebrew version](https://img.shields.io/homebrew/v/gocryptfs.svg)](https://formulae.brew.sh/formula/gocryptfs#default)
|
||||
libgocryptfs is a re-design of the original [gocryptfs](https://github.com/rfjakob/gocryptfs) code to work as a library. Volumes are not mounted with [FUSE](https://www.kernel.org/doc/html/latest/filesystems/fuse.html) but rather opened in memory and accessed through API calls. What the purpose ?
|
||||
- Allow the use of gocryptfs in embedded devices where FUSE is not available (such as Android)
|
||||
- Reduce attack surface by restricting volumes access to only one process rather than one user
|
||||
|
||||
An encrypted overlay filesystem written in Go.
|
||||
Official website: https://nuetzlich.net/gocryptfs ([markdown source](https://github.com/rfjakob/gocryptfs-website/blob/master/docs/index.md)).
|
||||
|
||||
![Folders side-by-side animation](Documentation/folders-side-by-side.gif)
|
||||
|
||||
gocryptfs is built on top the excellent
|
||||
[go-fuse](https://github.com/hanwen/go-fuse) FUSE library.
|
||||
This project was inspired by EncFS and strives to fix its security
|
||||
issues while providing good performance
|
||||
([benchmarks](https://nuetzlich.net/gocryptfs/comparison/#performance)).
|
||||
For details on the security of gocryptfs see the
|
||||
[Security](https://nuetzlich.net/gocryptfs/security/) design document.
|
||||
|
||||
All tags from v0.4 onward are signed by the *gocryptfs signing key*.
|
||||
Please check [Signed Releases](https://nuetzlich.net/gocryptfs/releases/)
|
||||
for details.
|
||||
|
||||
Current Status
|
||||
--------------
|
||||
|
||||
gocryptfs has reached version 1.0 on July 17, 2016. It has gone through
|
||||
hours and hours of stress (fsstress, extractloop.bash) and correctness
|
||||
testing (xfstests). It is now considered ready for general consumption.
|
||||
|
||||
The old principle still applies: Important data should have a backup.
|
||||
Also, keep a copy of your master key (printed on mount) in a safe place.
|
||||
This allows you to access the data even if the gocryptfs.conf config
|
||||
file is damaged or you lose the password.
|
||||
|
||||
The security of gocryptfs has been audited in March 3, 2017. The audit
|
||||
is available [here (defuse.ca)](https://defuse.ca/audits/gocryptfs.htm).
|
||||
|
||||
Platforms
|
||||
---------
|
||||
|
||||
Linux is gocryptfs' native platform.
|
||||
|
||||
Beta-quality macOS 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 macOS 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. The `fuse` package from your
|
||||
distribution must be installed for mounting to work.
|
||||
|
||||
gocryptfs is also available as a package in most distributions. Examples:
|
||||
|
||||
* Debian, Ubuntu: `apt install gocryptfs`
|
||||
* Arch: `pacman -S gocryptfs`
|
||||
* MacPorts: `port install 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.13 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 with AES-NI:
|
||||
|
||||
```
|
||||
$ ./gocryptfs -speed
|
||||
gocryptfs v2.2.0-beta1-5-g52b0444-dirty; go-fuse v2.1.1-0.20210825171523-3ab5d95a30ae; 2021-09-14 go1.17.1 linux/amd64
|
||||
cpu: Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz; with AES acceleration
|
||||
AES-GCM-256-OpenSSL 862.79 MB/s
|
||||
AES-GCM-256-Go 997.71 MB/s (selected in auto mode)
|
||||
AES-SIV-512-Go 159.58 MB/s
|
||||
XChaCha20-Poly1305-OpenSSL 729.65 MB/s
|
||||
XChaCha20-Poly1305-Go 843.97 MB/s (selected in auto mode)
|
||||
```
|
||||
|
||||
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.4.0, 2023-06-10
|
||||
* Try the `mount(2)` syscall before falling back to `fusermount(1)`. This means we
|
||||
don't need `fusermount(1)` at all if running as root or in a root-like namespace
|
||||
([#697](https://github.com/rfjakob/gocryptfs/issues/697))
|
||||
* Fix `-extpass` mis-parsing commas ([#730](https://github.com/rfjakob/gocryptfs/issues/730))
|
||||
* Fix `rm -R` mis-reporting `write-protected directory` on gocryptfs on sshfs
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/09954c4bdecf0ca6da65776f176dc934ffced2b0))
|
||||
|
||||
#### v2.3.2, 2023-04-29
|
||||
* Fix incorrect file size reported after hard link creation
|
||||
([#724](https://github.com/rfjakob/gocryptfs/issues/724))
|
||||
|
||||
#### v2.3.1, 2023-03-04
|
||||
* Optimize NFS streaming write performance ([#712](https://github.com/rfjakob/gocryptfs/issues/712),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/8f3ec5dcaa6eb18d11746675190a7aaceb422764)).
|
||||
You should see about a 4x performance increase.
|
||||
* Use `debug.ReadBuildInfo()` to provide some
|
||||
version information even when not built with `build.bash` ([#701](https://github.com/rfjakob/gocryptfs/pull/701)) .
|
||||
* Fix bug that caused the `logger` process to be killed when started from `xfce4-terminal`,
|
||||
and that terminal window was closed ([#660](https://github.com/rfjakob/gocryptfs/issues/660),
|
||||
[commit](https://github.com/rfjakob/gocryptfs/commit/ff32e9979130e6237b0d97ef88304fa79ce61b06)).
|
||||
* MacOS: Fix reverse mount failing with `read-only file system` ([#690](https://github.com/rfjakob/gocryptfs/pull/690))
|
||||
* Make gocryptfs compile on riscv64 by switching from [jacobsa/crypto](https://github.com/jacobsa/crypto)
|
||||
to maintained fork [aperturerobotics/jacobsa-crypto](https://github.com/aperturerobotics/jacobsa-crypto)
|
||||
([#674](https://github.com/rfjakob/gocryptfs/pull/674))
|
||||
|
||||
#### v2.3.0, 2022-10-21
|
||||
* Identical to v2.3, just tagged once more in full semver x.y.z format. This make Go's fetching logic happy,
|
||||
which ignores v2.3 (without the third digit) completely.
|
||||
Fixes [#694](https://github.com/rfjakob/gocryptfs/issues/694), [#688](https://github.com/rfjakob/gocryptfs/issues/688).
|
||||
|
||||
#### v2.3, 2022-08-28
|
||||
* Add **`-longnamemax`** flag to `-init` ([#499](https://github.com/rfjakob/gocryptfs/issues/499)).
|
||||
Can be used to work around file or path length restrictions on online storage.
|
||||
See the [man page](https://github.com/rfjakob/gocryptfs/blob/master/Documentation/MANPAGE.md#-longnamemax)
|
||||
for details.
|
||||
* Support for [`NO_COLOR`](https://no-color.org/) env variable ([#617](https://github.com/rfjakob/gocryptfs/issues/617))
|
||||
* Fix `-force_owner` not not affecting socket files ([#629](https://github.com/rfjakob/gocryptfs/issues/629)
|
||||
* MacOS: fix inaccessible `gocryptfs.conf` in reverse mode ([commit](https://github.com/rfjakob/gocryptfs/commit/c9e4e4f74150d2734496e90a4c442a17b79f52c1))
|
||||
* Raise ctlsock operation timeout from 1 to 10 seconds ([#683](https://github.com/rfjakob/gocryptfs/issues/683))
|
||||
|
||||
#### v2.2.1, 2021-10-20
|
||||
* Fix `-force_owner` only taking effect after 2 seconds ([#609](https://github.com/rfjakob/gocryptfs/issues/609)).
|
||||
This was a regression introduced in v2.0.
|
||||
* MacOS: Fix build.bash failure with error `date: illegal option -- -` when `SOURCE_DATE_EPOCH` is set
|
||||
([#570](https://github.com/rfjakob/gocryptfs/issues/570))
|
||||
* `-init`: suggest xchacha on CPUs without AES acceleration ([commit](https://github.com/rfjakob/gocryptfs/commit/e8e35982845f36e714b915350eaf6855487aa0e8))
|
||||
* `-info`: add contentEncryption to output
|
||||
|
||||
#### v2.2.0, 2021-09-25
|
||||
* **`-deterministic-names`: new option for `-init`**, both for reverse and forward mode.
|
||||
Disables file name randomisation & `gocryptfs.diriv` files
|
||||
([#151](https://github.com/rfjakob/gocryptfs/issues/151), [#402](https://github.com/rfjakob/gocryptfs/issues/402), [#592](https://github.com/rfjakob/gocryptfs/pull/592))
|
||||
* New feature flag! You need gocryptfs v2.2 or higher to mount a filesystem that uses this flag.
|
||||
* **`-xchacha`: new option for `-init`** (forward mode only). Selects XChaCha20-Poly1305 for content encryption.
|
||||
Gives *much* better performance on CPUs without AES acceleration
|
||||
([#452](https://github.com/rfjakob/gocryptfs/issues/452)).
|
||||
* New feature flag! You need gocryptfs v2.2 or higher to mount a filesystem that uses this flag.
|
||||
* Test with `gocryptfs -speed` what is fastest for your CPU, or read [here](https://github.com/rfjakob/gocryptfs/issues/452#issuecomment-908559414)
|
||||
* Rewrite [OpenSSL backend](https://pkg.go.dev/github.com/rfjakob/gocryptfs/v2/internal/stupidgcm)
|
||||
for better performance on AES-GCM-256-OpenSSL and XChaCha20-Poly1305-OpenSSL
|
||||
* `-serialize_reads`: get rid of delay logic by taking advantage of the kernel flag
|
||||
`FUSE_CAP_ASYNC_READ`
|
||||
([go-fuse commit](https://github.com/hanwen/go-fuse/commit/15a8bb029a4e1a51e10043c370970596b1fbb737),
|
||||
[gocryptfs commit](https://github.com/rfjakob/gocryptfs/commit/a99051b32452c9a781efe248c0014b65d4abddf7))
|
||||
* Make obsolete `-devrandom` flag a no-op ([commit](https://github.com/rfjakob/gocryptfs/commit/61ef6b00a675456ee05d40f1ce44d693bc4be350))
|
||||
* Make `-forcedecode` flag a no-op ([commit](https://github.com/rfjakob/gocryptfs/commit/d023cd6c95fcbc6b5056ba1f425d2ac3df4abc5a))
|
||||
* Fix reverse mode sometimes remapping most inode numbers to >281474976710656 ([commit](https://github.com/rfjakob/gocryptfs/commit/c9b825c58a9f996379108926754513bca03bb306))
|
||||
* This version will be called v2.2.0 (instead of v2.2) to comply with
|
||||
the [Go module versioning](https://golang.org/doc/modules/version-numbers) convention.
|
||||
Later releases will also follow the convention.
|
||||
|
||||
#### v2.1, 2021-08-18
|
||||
* `-fido2`: do not request PIN on `gocryptfs -init` fixing `FIDO_ERR_UNSUPPORTED_OPTION` with YubiKey
|
||||
([#571](https://github.com/rfjakob/gocryptfs/issues/571))
|
||||
* `-sharedstorage`: present stable inode numbers, fixing getcwd failures
|
||||
([#584](https://github.com/rfjakob/gocryptfs/issues/584))
|
||||
* `-badname`: make it possible to access content of invalid file names ([#568](https://github.com/rfjakob/gocryptfs/pull/568)).
|
||||
Thanks @DerDonut!
|
||||
* Implement recursive `gocryptfs.diriv` caching to fix exponential runtime with deep directories
|
||||
([commit](https://github.com/rfjakob/gocryptfs/commit/84e702126ac4f017e12150532bfaed675dee2927)])
|
||||
* Implements fsync on directories ([#587](https://github.com/rfjakob/gocryptfs/issues/587))
|
||||
* `-reverse`: implement `-one-file-system` ([#475](https://github.com/rfjakob/gocryptfs/issues/475))
|
||||
* `-reverse`: allow exclude-all-but ([#588](https://github.com/rfjakob/gocryptfs/issues/588))
|
||||
* Example: `gocryptfs -reverse -exclude-wildcard '*' -exclude-wildcard '!/my-important-files' /home/user /mnt/user.encrypted`
|
||||
* macOS: Fix `panic: using reserved ID 1` on ExFAT ([#585](https://github.com/rfjakob/gocryptfs/issues/585))
|
||||
* Switch to `pflag` cli parsing library to support flags and arguments in any order
|
||||
([#590](https://github.com/rfjakob/gocryptfs/issues/590))
|
||||
* Drop support for Go 1.11 & Go 1.12 ([commit](https://github.com/rfjakob/gocryptfs/commit/a5f88e86d186cdbc67e1efabd7aacf389775e027))
|
||||
* You must have Go 1.13 or newer now
|
||||
|
||||
#### 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/aperturerobotics/jacobsa-crypto) library.
|
||||
The corresponding feature flag is called `AESSIV`.
|
||||
* New command-line options: `-reverse`, `-aessiv`
|
||||
* Filesystems using reverse mode can only be mounted with gocryptfs v1.1
|
||||
and later.
|
||||
* The default, forward mode, stays fully compatible with older versions.
|
||||
Forward mode will keep using GCM because it is much faster.
|
||||
* Accept `-o foo,bar,baz`-style options that are passed at the end of
|
||||
the command-line, like mount(1) does. All other options must still
|
||||
precede the passed paths.
|
||||
* This allows **mounting from /etc/fstab**. See
|
||||
[#45](https://github.com/rfjakob/gocryptfs/issues/45) for details.
|
||||
* **Mounting on login using pam_mount** works as well. It is
|
||||
[described in the wiki](https://github.com/rfjakob/gocryptfs/wiki/Mounting-on-login-using-pam_mount).
|
||||
* To prevent confusion, the old `-o` option had to be renamed. It is now
|
||||
called `-ko`. Arguments to `-ko` are passed directly to the kernel.
|
||||
* New `-passfile` command-line option. Provides an easier way to read
|
||||
the password from a file. Internally, this is equivalent to
|
||||
`-extpass "/bin/cat FILE"`.
|
||||
* Enable changing the password when you only know the master key
|
||||
([#28](https://github.com/rfjakob/gocryptfs/issues/28))
|
||||
|
||||
#### v1.0, 2016-07-17
|
||||
* Deprecate very old filesystems, stage 3/3
|
||||
* Filesystems created by v0.6 can no longer be mounted
|
||||
* Drop command-line options `-gcmiv128`, `-emenames`, `-diriv`. These
|
||||
are now always enabled.
|
||||
* Add fallocate(2) support
|
||||
* New command-line option `-o`
|
||||
* Allows to pass mount options directly to the kernel
|
||||
* Add support for device files and suid binaries
|
||||
* Only works when running as root
|
||||
* Must be explicitly enabled by passing "-o dev" or "-o suid" or "-o suid,dev"
|
||||
* Experimental Mac OS X support. See
|
||||
[ticket #15](https://github.com/rfjakob/gocryptfs/issues/15) for details.
|
||||
|
||||
#### v0.12, 2016-06-19
|
||||
* Deprecate very old filesystems, stage 2/3
|
||||
* Filesystems created by v0.6 and older can only be mounted read-only
|
||||
* A [message](https://github.com/rfjakob/gocryptfs/blob/v0.12/internal/configfile/config_file.go#L120)
|
||||
explaining the situation is printed as well
|
||||
* New command line option: `-ro`
|
||||
* Mounts the filesystem read-only
|
||||
* Accept password from stdin as well ([ticket #30](https://github.com/rfjakob/gocryptfs/issues/30))
|
||||
|
||||
#### v0.11, 2016-06-10
|
||||
* Deprecate very old filesystems, stage 1/3
|
||||
* Filesystems created by v0.6 and older can still be mounted but a
|
||||
[warning](https://github.com/rfjakob/gocryptfs/blob/v0.11/internal/configfile/config_file.go#L120)
|
||||
is printed
|
||||
* See [ticket #29](https://github.com/rfjakob/gocryptfs/issues/29) for details and
|
||||
join the discussion
|
||||
* Add rsync stress test "pingpong-rsync.bash"
|
||||
* Fix chown and utimens failures that caused rsync to complain
|
||||
* Build release binaries with Go 1.6.2
|
||||
* Big speedup for CPUs with AES-NI, see [ticket #23](https://github.com/rfjakob/gocryptfs/issues/23)
|
||||
|
||||
#### v0.10, 2016-05-30
|
||||
* **Replace `spacemonkeygo/openssl` with `stupidgcm`**
|
||||
* gocryptfs now has its own thin wrapper to OpenSSL's GCM implementation
|
||||
called `stupidgcm`.
|
||||
* This should fix the [compile issues](https://github.com/rfjakob/gocryptfs/issues/21)
|
||||
people are seeing with `spacemonkeygo/openssl`. It also gets us
|
||||
a 20% performance boost for streaming writes.
|
||||
* **Automatically choose between OpenSSL and Go crypto** [issue #23](https://github.com/rfjakob/gocryptfs/issues/23)
|
||||
* Go 1.6 added an optimized GCM implementation in amd64 assembly that uses AES-NI.
|
||||
This is faster than OpenSSL and is used if available. In all other
|
||||
cases OpenSSL is much faster and is used instead.
|
||||
* `-openssl=auto` is the new default
|
||||
* Passing `-openssl=true/false` overrides the autodetection.
|
||||
* Warn but continue anyway if fallocate(2) is not supported by the
|
||||
underlying filesystem, see [issue #22](https://github.com/rfjakob/gocryptfs/issues/22)
|
||||
* Enables to use gocryptfs on ZFS and ext3, albeit with reduced out-of-space safety.
|
||||
* [Fix statfs](https://github.com/rfjakob/gocryptfs/pull/27), by @lxp
|
||||
* Fix a fsstress [failure](https://github.com/hanwen/go-fuse/issues/106)
|
||||
in the go-fuse library.
|
||||
|
||||
#### v0.9, 2016-04-10
|
||||
* **Long file name support**
|
||||
* gocryptfs now supports file names up to 255 characters.
|
||||
* This is a forwards-compatible change. gocryptfs v0.9 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* Refactor gocryptfs into multiple "internal" packages
|
||||
* New command-line options:
|
||||
* `-longnames`: Enable long file name support (default true)
|
||||
* `-nosyslog`: Print messages to stdout and stderr instead of syslog (default false)
|
||||
* `-wpanic`: Make warning messages fatal (used for testing)
|
||||
* `-d`: Alias for `-debug`
|
||||
* `-q`: Alias for `-quiet`
|
||||
|
||||
#### v0.8, 2016-01-23
|
||||
* Redirect output to syslog when running in the background
|
||||
* New command-line option:
|
||||
* `-memprofile`: Write a memory allocation debugging profile the specified
|
||||
file
|
||||
|
||||
#### v0.7.2, 2016-01-19
|
||||
* **Fix performance issue in small file creation**
|
||||
* This brings performance on-par with EncFS paranoia mode, with streaming writes
|
||||
significantly faster
|
||||
* The actual [fix](https://github.com/hanwen/go-fuse/commit/c4b6b7949716d13eec856baffc7b7941ae21778c)
|
||||
is in the go-fuse library. There are no code changes in gocryptfs.
|
||||
|
||||
#### v0.7.1, 2016-01-09
|
||||
* Make the `build.bash` script compatible with Go 1.3
|
||||
* Disable fallocate on OSX (system call not available)
|
||||
* Introduce pre-built binaries for Fedora 23 and Debian 8
|
||||
|
||||
#### v0.7, 2015-12-20
|
||||
* **Extend GCM IV size to 128 bit from Go's default of 96 bit**
|
||||
* This pushes back the birthday bound to make IV collisions virtually
|
||||
impossible
|
||||
* This is a forwards-compatible change. gocryptfs v0.7 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* New command-line option:
|
||||
* `-gcmiv128`: Use 128-bit GCM IVs (default true)
|
||||
|
||||
#### v0.6, 2015-12-08
|
||||
* **Wide-block filename encryption using EME + DirIV**
|
||||
* EME (ECB-Mix-ECB) provides even better security than CBC as it fixes
|
||||
the prefix leak. The used Go EME implementation is
|
||||
https://github.com/rfjakob/eme which is, as far as I know, the first
|
||||
implementation of EME in Go.
|
||||
* This is a forwards-compatible change. gocryptfs v0.6 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* New command-line option:
|
||||
* `-emenames`: Enable EME filename encryption (default true)
|
||||
|
||||
#### v0.5.1, 2015-12-06
|
||||
* Fix a rename regression caused by DirIV and add test case
|
||||
* Use fallocate to guard against out-of-space errors
|
||||
|
||||
#### v0.5, 2015-12-04
|
||||
* **Stronger filename encryption: DirIV**
|
||||
* Each directory gets a random 128 bit file name IV on creation,
|
||||
stored in `gocryptfs.diriv`
|
||||
* This makes it impossible to identify identically-named files across
|
||||
directories
|
||||
* A single-entry IV cache brings the performance cost of DirIV close to
|
||||
zero for common operations (see performance.txt)
|
||||
* This is a forwards-compatible change. gocryptfs v0.5 can mount filesystems
|
||||
created by earlier versions but not the other way round.
|
||||
* New command-line option:
|
||||
* `-diriv`: Use the new per-directory IV file name encryption (default true)
|
||||
* `-scryptn`: allows to set the scrypt cost parameter N. This option
|
||||
can be used for faster mounting at the cost of lower brute-force
|
||||
resistance. It was mainly added to speed up the automated tests.
|
||||
|
||||
#### v0.4, 2015-11-15
|
||||
* New command-line options:
|
||||
* `-plaintextnames`: disables filename encryption, added on user request
|
||||
* `-extpass`: calls an external program for prompting for the password
|
||||
* `-config`: allows to specify a custom gocryptfs.conf path
|
||||
* Add `FeatureFlags` gocryptfs.conf parameter
|
||||
* This is a config format change, hence the on-disk format is incremented
|
||||
* Used for ext4-style filesystem feature flags. This should help avoid future
|
||||
format changes. The first user is `-plaintextnames`.
|
||||
* On-disk format 2
|
||||
|
||||
#### v0.3, 2015-11-01
|
||||
* **Add a random 128 bit file header to authenticate file->block ownership**
|
||||
* This is an on-disk-format change
|
||||
* On-disk format 1
|
||||
|
||||
#### v0.2, 2015-10-11
|
||||
* Replace bash daemonization wrapper with native Go implementation
|
||||
* Better user feedback on mount failures
|
||||
|
||||
#### v0.1, 2015-10-07
|
||||
* First release
|
||||
* On-disk format 0
|
||||
## Warning !
|
||||
The only goal of this library is to be integrated in [DroidFS](https://forge.chapril.org/hardcoresushi/DroidFS). It's not actually ready for other usages. libgocryptfs doesn't implement all features provided by gocryptfs like symbolic links, editing attributes, creating reverse volume... Use it at your own risk !
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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).
|
||||
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 {} +
|
106
benchmark.bash
106
benchmark.bash
|
@ -1,106 +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
|
||||
|
||||
usage() {
|
||||
echo "Usage: $MYNAME [-encfs] [-openssl=true] [-openssl=false] [-dd] [DIR]"
|
||||
}
|
||||
|
||||
OPT_ENCFS=0
|
||||
OPT_LOOPBACK=0
|
||||
OPT_OPENSSL=""
|
||||
OPT_DIR=""
|
||||
DD_ONLY=""
|
||||
OPT_XCHACHA=""
|
||||
|
||||
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
|
||||
;;
|
||||
-xchacha)
|
||||
OPT_XCHACHA="-xchacha"
|
||||
;;
|
||||
-*)
|
||||
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 $OPT_XCHACHA $OPT_OPENSSL at $CRYPT: "
|
||||
gocryptfs -version
|
||||
gocryptfs $OPT_XCHACHA -q -init -extpass="echo test" -scryptn=10 "$CRYPT"
|
||||
gocryptfs $OPT_OPENSSL -q -extpass="echo test" "$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
|
98
build.bash
98
build.bash
|
@ -1,98 +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")"
|
||||
|
||||
# $0 does not work because we may have been sourced
|
||||
MYNAME=build.bash
|
||||
|
||||
# Make sure we have the go binary
|
||||
go version > /dev/null
|
||||
|
||||
# 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 "$MYNAME: 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 "$MYNAME: 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
|
||||
if ! BUILDDATE=$(date -u --date="@${SOURCE_DATE_EPOCH}" +%Y-%m-%d) ; then
|
||||
echo "$MYNAME: info: retrying with BSD date syntax..."
|
||||
BUILDDATE=$(date -u -r "$SOURCE_DATE_EPOCH" +%Y-%m-%d)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Only set GOFLAGS if it is not already set by the user
|
||||
if [[ -z ${GOFLAGS:-} ]] ; then
|
||||
GOFLAGS="-trimpath"
|
||||
# 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"
|
|
@ -0,0 +1,86 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ -z ${ANDROID_NDK_HOME+x} ]; then
|
||||
echo "Error: \$ANDROID_NDK_HOME is not defined." >&2
|
||||
exit 1
|
||||
else
|
||||
NDK_BIN_PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
declare -a ABIs=("x86_64" "x86" "arm64-v8a" "armeabi-v7a")
|
||||
|
||||
invalid_abi() {
|
||||
echo "Invalid ABI: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
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
|
||||
invalid_abi $1
|
||||
fi
|
||||
if [ -z ${OPENSSL_PATH+x} ]; then
|
||||
echo "Error: \$OPENSSL_PATH is not defined." >&2
|
||||
exit 1
|
||||
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 -j $(nproc --all) build_libs
|
||||
) &&
|
||||
mkdir -p "./lib/$1" "./include/$1" &&
|
||||
cp "$OPENSSL_PATH/libcrypto.a" "$OPENSSL_PATH/libssl.a" "./lib/$1" &&
|
||||
cp -r "$OPENSSL_PATH"/include/* "./include/$1/" || exit 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
|
||||
invalid_abi $1
|
||||
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 || exit 1
|
||||
}
|
||||
|
||||
cd $(dirname $0) || exit 1
|
||||
if [ "$#" -eq 1 ]; then
|
||||
compile_for_arch $1
|
||||
else
|
||||
for abi in ${ABIs[@]}; do
|
||||
echo "Compiling for $abi..."
|
||||
compile_for_arch $abi
|
||||
done
|
||||
fi
|
||||
fi
|
347
cli_args.go
347
cli_args.go
|
@ -1,347 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
// Should be initialized before anything else.
|
||||
// This import line MUST be in the alphabetically first source code file of
|
||||
// package main!
|
||||
_ "github.com/rfjakob/gocryptfs/v2/internal/ensurefds012"
|
||||
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/v2/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, hh, info,
|
||||
sharedstorage, fsck, one_file_system, deterministic_names,
|
||||
xchacha 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 []string
|
||||
// For reverse mode, several ways to specify exclusions. All can be specified multiple times.
|
||||
exclude, excludeWildcard, excludeFrom []string
|
||||
// Configuration file name override
|
||||
config string
|
||||
notifypid, scryptn int
|
||||
// Idle time before autounmount
|
||||
idle time.Duration
|
||||
// -longnamemax (hash encrypted names that are longer than this)
|
||||
longnamemax uint8
|
||||
// Helper variables that are NOT cli options all start with an underscore
|
||||
// _configCustom is true when the user sets a custom config file name.
|
||||
_configCustom bool
|
||||
// _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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// convertToDoubleDash converts args like "-debug" (Go stdlib `flag` style)
|
||||
// into "--debug" (spf13/pflag style).
|
||||
// gocryptfs v2.1 switched from `flag` to `pflag`, but we obviously want to stay
|
||||
// cli-compatible, and this is the hack to do it.
|
||||
//
|
||||
// BUG: In `-extpass -X`, the `-X` gets transformed `--X`.
|
||||
// See "Dash duplication" in the man page and
|
||||
// https://github.com/rfjakob/gocryptfs/issues/621 .
|
||||
func convertToDoubleDash(osArgs []string) (out []string) {
|
||||
out = append(out, osArgs...)
|
||||
for i, v := range out {
|
||||
// Leave "-h" alone so the help text keeps working
|
||||
if v == "-h" {
|
||||
continue
|
||||
}
|
||||
// Don't touch anything after "--"
|
||||
if v == "--" {
|
||||
break
|
||||
}
|
||||
// Convert "-foo" to "--foo"
|
||||
if len(v) >= 2 && v[0] == '-' && v[1] != '-' {
|
||||
out[i] = "-" + out[i]
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// parseCliOpts - parse command line options (i.e. arguments that start with "-")
|
||||
func parseCliOpts(osArgs []string) (args argContainer) {
|
||||
var err error
|
||||
var opensslAuto string
|
||||
|
||||
osArgsPreprocessed, err := prefixOArgs(osArgs)
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
osArgsPreprocessed = convertToDoubleDash(osArgsPreprocessed)
|
||||
|
||||
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 175 bytes in extra files")
|
||||
flagSet.BoolVar(&args.allow_other, "allow_other", false, "Allow other users to access the filesystem. "+
|
||||
"Only works if user_allow_other is set in /etc/fuse.conf.")
|
||||
flagSet.BoolVar(&args.reverse, "reverse", false, "Reverse mode")
|
||||
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.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.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR")
|
||||
flagSet.BoolVar(&args.one_file_system, "one-file-system", false, "Don't cross filesystem boundaries")
|
||||
flagSet.BoolVar(&args.deterministic_names, "deterministic-names", false, "Disable diriv file name randomisation")
|
||||
flagSet.BoolVar(&args.xchacha, "xchacha", false, "Use XChaCha20-Poly1305 file content encryption")
|
||||
|
||||
// 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.StringArrayVar(&args.exclude, "e", nil, "Alias for -exclude")
|
||||
flagSet.StringArrayVar(&args.exclude, "exclude", nil, "Exclude relative path from reverse view")
|
||||
flagSet.StringArrayVar(&args.excludeWildcard, "ew", nil, "Alias for -exclude-wildcard")
|
||||
flagSet.StringArrayVar(&args.excludeWildcard, "exclude-wildcard", nil, "Exclude path from reverse view, supporting wildcards")
|
||||
flagSet.StringArrayVar(&args.excludeFrom, "exclude-from", nil, "File from which to read exclusion patterns (with -exclude-wildcard syntax)")
|
||||
|
||||
// multipleStrings options ([]string)
|
||||
flagSet.StringArrayVar(&args.extpass, "extpass", nil, "Use external program for the password prompt")
|
||||
flagSet.StringArrayVar(&args.badname, "badname", nil, "Glob pattern invalid file names that should be shown")
|
||||
flagSet.StringArrayVar(&args.passfile, "passfile", nil, "Read password from file")
|
||||
|
||||
flagSet.Uint8Var(&args.longnamemax, "longnamemax", 255, "Hash encrypted names that are longer than this")
|
||||
|
||||
flagSet.IntVar(&args.notifypid, "notifypid", 0, "Send USR1 to the specified process after "+
|
||||
"successful mount - used internally for daemonization")
|
||||
const scryptn = "scryptn"
|
||||
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 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.")
|
||||
|
||||
// Ignored flags
|
||||
{
|
||||
var tmp bool
|
||||
flagSet.BoolVar(&tmp, "nofail", false, "Ignored for /etc/fstab compatibility")
|
||||
flagSet.BoolVar(&tmp, "devrandom", false, "Obsolete, ignored for compatibility")
|
||||
flagSet.BoolVar(&tmp, "forcedecode", false, "Obsolete, ignored for compatibility")
|
||||
}
|
||||
|
||||
// Actual parsing
|
||||
err = flagSet.Parse(osArgsPreprocessed[1:])
|
||||
if err == flag.ErrHelp {
|
||||
helpShort()
|
||||
os.Exit(0)
|
||||
}
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("Invalid command line: %s: %v. Try '%s -help'.", prettyArgs(), err, 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" {
|
||||
if args.xchacha {
|
||||
args.openssl = stupidgcm.PreferOpenSSLXchacha20poly1305()
|
||||
} else {
|
||||
args.openssl = stupidgcm.PreferOpenSSLAES256GCM()
|
||||
}
|
||||
} else {
|
||||
args.openssl, err = strconv.ParseBool(opensslAuto)
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("Invalid \"-openssl\" setting: %v", err)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
}
|
||||
if len(args.extpass) > 0 && 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 len(args.extpass) > 0 && args.masterkey != "" {
|
||||
tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
if len(args.extpass) > 0 && 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)
|
||||
}
|
||||
// Make sure all badname patterns are valid
|
||||
for _, pattern := range args.badname {
|
||||
_, err := filepath.Match(pattern, "")
|
||||
if err != nil {
|
||||
tlog.Fatal.Printf("-badname: invalid pattern %q supplied", pattern)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
}
|
||||
if args.longnamemax > 0 && args.longnamemax < 62 {
|
||||
tlog.Fatal.Printf("-longnamemax: value %d is outside allowed range 62 ... 255", args.longnamemax)
|
||||
os.Exit(exitcodes.Usage)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// prettyArgs pretty-prints the command-line arguments.
|
||||
func prettyArgs() string {
|
||||
pa := fmt.Sprintf("%q", 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 explicitly 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
|
||||
}
|
180
cli_args_test.go
180
cli_args_test.go
|
@ -1,180 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
|
||||
)
|
||||
|
||||
// TestPrefixOArgs checks that the "-o x,y,z" parsing works correctly.
|
||||
func TestPrefixOArgs(t *testing.T) {
|
||||
testcases := []struct {
|
||||
// i is the input
|
||||
i []string
|
||||
// o is the expected output
|
||||
o []string
|
||||
// Do we expect an error?
|
||||
e bool
|
||||
}{
|
||||
{
|
||||
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 TestConvertToDoubleDash(t *testing.T) {
|
||||
testcases := []struct {
|
||||
// i is the input
|
||||
i []string
|
||||
// o is the expected output
|
||||
o []string
|
||||
}{
|
||||
{
|
||||
i: nil,
|
||||
o: nil,
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs"},
|
||||
o: []string{"gocryptfs"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "foo"},
|
||||
o: []string{"gocryptfs", "foo"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "-v", "-quiet"},
|
||||
o: []string{"gocryptfs", "--v", "--quiet"},
|
||||
},
|
||||
{
|
||||
i: []string{"gocryptfs", "--", "-foo"},
|
||||
o: []string{"gocryptfs", "--", "-foo"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
o := convertToDoubleDash(tc.i)
|
||||
if !reflect.DeepEqual(o, tc.o) {
|
||||
t.Errorf("in=%q\nwant=%q\nhave=%q", tc.i, tc.o, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCliOpts(t *testing.T) {
|
||||
defaultArgs := argContainer{
|
||||
longnames: true,
|
||||
longnamemax: 255,
|
||||
raw64: true,
|
||||
hkdf: true,
|
||||
openssl: stupidgcm.PreferOpenSSLAES256GCM(), // depends on CPU and build flags
|
||||
scryptn: 16,
|
||||
}
|
||||
|
||||
type testcaseContainer struct {
|
||||
// i is the input
|
||||
i []string
|
||||
// o is the expected output
|
||||
o argContainer
|
||||
}
|
||||
|
||||
testcases := []testcaseContainer{
|
||||
{
|
||||
i: []string{"gocryptfs"},
|
||||
o: defaultArgs,
|
||||
},
|
||||
}
|
||||
|
||||
o := defaultArgs
|
||||
o.quiet = true
|
||||
testcases = append(testcases, []testcaseContainer{
|
||||
{
|
||||
i: []string{"gocryptfs", "-q"},
|
||||
o: o,
|
||||
}, {
|
||||
i: []string{"gocryptfs", "--q"},
|
||||
o: o,
|
||||
}, {
|
||||
i: []string{"gocryptfs", "-quiet"},
|
||||
o: o,
|
||||
}, {
|
||||
i: []string{"gocryptfs", "--quiet"},
|
||||
o: o,
|
||||
},
|
||||
}...)
|
||||
|
||||
o = defaultArgs
|
||||
o.exclude = []string{"foo", "bar", "baz,boe"}
|
||||
testcases = append(testcases, []testcaseContainer{
|
||||
{
|
||||
i: []string{"gocryptfs", "-e", "foo", "-e", "bar", "-e", "baz,boe"},
|
||||
o: o,
|
||||
}, {
|
||||
i: []string{"gocryptfs", "--exclude", "foo", "--exclude", "bar", "--exclude", "baz,boe"},
|
||||
o: o,
|
||||
}, /* TODO BROKEN {
|
||||
i: []string{"gocryptfs", "--exclude", "foo", "-e", "bar"},
|
||||
o: o,
|
||||
},*/
|
||||
}...)
|
||||
|
||||
for _, tc := range testcases {
|
||||
o := parseCliOpts(tc.i)
|
||||
if !reflect.DeepEqual(o, tc.o) {
|
||||
t.Errorf("in=%v\nwant=%v\nhave=%v", tc.i, tc.o, o)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
tenets:
|
||||
- import: codelingo/effective-go
|
||||
- import: codelingo/code-review-comments
|
||||
- import: codelingo/rfjakob-gocryptfs
|
|
@ -0,0 +1,96 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"syscall"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"libgocryptfs/v2/internal/nametransform"
|
||||
"libgocryptfs/v2/internal/syscallcompat"
|
||||
)
|
||||
|
||||
//export gcf_get_attrs
|
||||
func gcf_get_attrs(sessionID int, relPath string) (uint32, uint64, uint64, bool) {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
dirfd, cName, err := volume.prepareAtSyscall(relPath)
|
||||
if err != nil {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
|
||||
st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
|
||||
// Translate ciphertext size to plaintext size
|
||||
size := volume.translateSize(dirfd, cName, st)
|
||||
|
||||
return st.Mode, size, uint64(st.Mtim.Sec), true
|
||||
}
|
||||
|
||||
// libgocryptfs: using Renameat instead of Renameat2 to support older kernels
|
||||
//export gcf_rename
|
||||
func gcf_rename(sessionID int, oldPath string, newPath string) bool {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
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.Renameat(dirfd, cName, dirfd2, cName2))
|
||||
}
|
||||
// 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.Renameat(dirfd, cName, dirfd2, cName2)
|
||||
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.Renameat(dirfd, cName, dirfd2, cName2)
|
||||
}
|
||||
}
|
||||
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 +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 occur 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 +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})
|
||||
break
|
||||
}
|
||||
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 `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: unknown 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/v2/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 +0,0 @@
|
|||
/getdents
|
|
@ -1,91 +0,0 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 +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,99 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Find out the maximum supported filename length and print it.
|
||||
#
|
||||
# Part of the gocryptfs test suite
|
||||
# https://nuetzlich.net/gocryptfs/
|
||||
|
||||
set -eu
|
||||
MYNAME=$(basename "$0")
|
||||
|
||||
if [[ $# -ne 1 || $1 == -* ]]; then
|
||||
echo "Usage: $MYNAME DIRECTORY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only show live progress if connected to a termial
|
||||
# https://stackoverflow.com/a/911213/1380267
|
||||
INTERACTIVE=0
|
||||
if [[ -t 1 ]] ; then
|
||||
INTERACTIVE=1
|
||||
fi
|
||||
|
||||
cd "$1"
|
||||
echo "Testing $PWD"
|
||||
|
||||
echo -n " Maximum filename length: "
|
||||
# Add one character at a time until we hit an error
|
||||
NAME=""
|
||||
while true ; do
|
||||
NEXT="${NAME}x"
|
||||
if [[ -e $NEXT ]]; then
|
||||
echo "error: file $PWD/$NEXT already exists"
|
||||
exit 1
|
||||
fi
|
||||
echo -n 2> /dev/null > "$NEXT" || break
|
||||
rm "$NEXT"
|
||||
NAME="$NEXT"
|
||||
done
|
||||
echo "${#NAME}"
|
||||
|
||||
# Set to 0 if undefined
|
||||
: ${QUICK:=0}
|
||||
|
||||
if [[ $QUICK -ne 1 ]]; then
|
||||
echo -n " Maximum dirname length: "
|
||||
# Add one character at a time until we hit an error
|
||||
NAME=""
|
||||
while true ; do
|
||||
NEXT="${NAME}x"
|
||||
mkdir "$NEXT" 2> /dev/null || break
|
||||
rmdir "$NEXT"
|
||||
NAME="$NEXT"
|
||||
done
|
||||
MAX_DIRNAME=${#NAME}
|
||||
echo "${#NAME}"
|
||||
fi
|
||||
|
||||
if [[ $QUICK -eq 1 ]]; then
|
||||
CHARS_TODO=100
|
||||
else
|
||||
CHARS_TODO="1 10 100 $MAX_DIRNAME"
|
||||
fi
|
||||
|
||||
for CHARS_PER_SUBDIR in $CHARS_TODO ; do
|
||||
echo -n " Maximum path length with $(printf %3d $CHARS_PER_SUBDIR) chars per subdir: "
|
||||
if [[ $INTERACTIVE -eq 1 ]] ; then
|
||||
echo -n " "
|
||||
fi
|
||||
# Trick from https://stackoverflow.com/a/5349842/1380267
|
||||
SUBDIR=$(printf 'x%.0s' $(seq 1 $CHARS_PER_SUBDIR))
|
||||
mkdir "$SUBDIR"
|
||||
P=$SUBDIR
|
||||
# Create a deep path, one $SUBDIR at a time, until we hit an error
|
||||
while true ; do
|
||||
NEXT="$P/$SUBDIR"
|
||||
mkdir "$NEXT" 2> /dev/null || break
|
||||
P=$NEXT
|
||||
if [[ $INTERACTIVE -eq 1 ]] ; then
|
||||
echo -n -e "\b\b\b\b"
|
||||
printf %4d ${#P}
|
||||
fi
|
||||
done
|
||||
# Then add one character at a time until we hit an error
|
||||
NAME=""
|
||||
while true ; do
|
||||
NEXT="${NAME}x"
|
||||
touch "$P/$NEXT" 2> /dev/null || break
|
||||
NAME=$NEXT
|
||||
done
|
||||
if [[ $NAME != "" ]] ; then
|
||||
P=$P/$NAME
|
||||
fi
|
||||
if [[ $INTERACTIVE -eq 1 ]] ; then
|
||||
echo -n -e "\b\b\b\b"
|
||||
fi
|
||||
printf %4d ${#P}
|
||||
echo
|
||||
rm -R "$SUBDIR"
|
||||
done
|
|
@ -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 +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 +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,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Build on all supported architectures & operating systems
|
||||
|
||||
function build {
|
||||
# Discard resulting binary by writing to /dev/null
|
||||
go build -tags without_openssl -o /dev/null
|
||||
}
|
||||
|
||||
function compile_tests {
|
||||
for i in $(go list ./...) ; do
|
||||
go test -c -tags without_openssl -o /dev/null "$i" > /dev/null
|
||||
done
|
||||
}
|
||||
|
||||
set -eux
|
||||
|
||||
export GO111MODULE=on
|
||||
export CGO_ENABLED=0
|
||||
|
||||
GOOS=linux GOARCH=amd64 build
|
||||
|
||||
# See https://github.com/golang/go/wiki/GoArm
|
||||
GOOS=linux GOARCH=arm GOARM=7 build
|
||||
GOOS=linux GOARCH=arm64 build
|
||||
|
||||
# MacOS on Intel
|
||||
GOOS=darwin GOARCH=amd64 build
|
||||
# Catch tests that don't work on MacOS (takes a long time so we only run it once)
|
||||
time GOOS=darwin GOARCH=amd64 compile_tests
|
||||
|
||||
# MacOS on Apple Silicon M1.
|
||||
# Go 1.16 added support for the M1 and added ios/arm64,
|
||||
# so we use this to check if we should attempt a build.
|
||||
if go tool dist list | grep ios/arm64 ; then
|
||||
GOOS=darwin GOARCH=arm64 build
|
||||
fi
|
|
@ -1,65 +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
|
||||
}
|
||||
|
||||
// There was at least one user who hit the earlier 1 second timeout. Raise to 10
|
||||
// seconds which ought to be enough for anyone.
|
||||
const ctlsockTimeout = 10 * time.Second
|
||||
|
||||
// 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, ctlsockTimeout)
|
||||
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(ctlsockTimeout))
|
||||
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/v2/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
|
||||
"github.com/rfjakob/gocryptfs/v2/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()
|
||||
}
|
|
@ -1,13 +1,10 @@
|
|||
package fusefrontend
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -23,8 +20,7 @@ const (
|
|||
)
|
||||
|
||||
type dirCacheEntry struct {
|
||||
// pointer to the Node this entry belongs to
|
||||
node *Node
|
||||
path string
|
||||
// fd to the directory (opened with O_PATH!)
|
||||
fd int
|
||||
// content of gocryptfs.diriv in this directory
|
||||
|
@ -37,13 +33,10 @@ func (e *dirCacheEntry) Clear() {
|
|||
// Note: package ensurefds012, imported from main, guarantees that dirCache
|
||||
// can never get fds 0,1,2.
|
||||
if e.fd > 0 {
|
||||
err := syscall.Close(e.fd)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("dirCache.Clear: Close failed: %v", err)
|
||||
}
|
||||
syscall.Close(e.fd)
|
||||
}
|
||||
e.fd = -1
|
||||
e.node = nil
|
||||
e.path = ""
|
||||
e.iv = nil
|
||||
}
|
||||
|
||||
|
@ -66,7 +59,6 @@ type dirCache struct {
|
|||
|
||||
// Clear clears the cache contents.
|
||||
func (d *dirCache) Clear() {
|
||||
d.dbg("Clear\n")
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
for i := range d.entries {
|
||||
|
@ -76,7 +68,7 @@ func (d *dirCache) Clear() {
|
|||
|
||||
// Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller
|
||||
// can close their copy at will.
|
||||
func (d *dirCache) Store(node *Node, fd int, iv []byte) {
|
||||
func (d *dirCache) Store(path string, fd int, iv []byte) {
|
||||
// Note: package ensurefds012, imported from main, guarantees that dirCache
|
||||
// can never get fds 0,1,2.
|
||||
if fd <= 0 || len(iv) != d.ivLen {
|
||||
|
@ -91,12 +83,10 @@ func (d *dirCache) Store(node *Node, fd int, iv []byte) {
|
|||
e.Clear()
|
||||
fd2, err := syscall.Dup(fd)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("dirCache.Store: Dup failed: %v", err)
|
||||
return
|
||||
}
|
||||
d.dbg("dirCache.Store %p fd=%d iv=%x\n", node, fd2, iv)
|
||||
e.fd = fd2
|
||||
e.node = node
|
||||
e.path = string([]byte(path[:]))
|
||||
e.iv = iv
|
||||
// expireThread is started on the first Lookup()
|
||||
if !d.expireThreadRunning {
|
||||
|
@ -108,7 +98,7 @@ func (d *dirCache) Store(node *Node, fd int, iv []byte) {
|
|||
// Lookup checks if relPath is in the cache, and returns an (fd, iv) pair.
|
||||
// It returns (-1, nil) if not found. The fd is internally Dup()ed and the
|
||||
// caller must close it when done.
|
||||
func (d *dirCache) Lookup(node *Node) (fd int, iv []byte) {
|
||||
func (d *dirCache) Lookup(path string) (fd int, iv []byte) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
if enableStats {
|
||||
|
@ -121,21 +111,19 @@ func (d *dirCache) Lookup(node *Node) (fd int, iv []byte) {
|
|||
// Cache slot is empty
|
||||
continue
|
||||
}
|
||||
if node != e.node {
|
||||
if path != e.path {
|
||||
// Not the right path
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
fd, err = syscall.Dup(e.fd)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("dirCache.Lookup: Dup failed: %v", err)
|
||||
return -1, nil
|
||||
}
|
||||
iv = e.iv
|
||||
break
|
||||
}
|
||||
if fd == 0 {
|
||||
d.dbg("dirCache.Lookup %p miss\n", node)
|
||||
return -1, nil
|
||||
}
|
||||
if enableStats {
|
||||
|
@ -144,7 +132,6 @@ func (d *dirCache) Lookup(node *Node) (fd int, iv []byte) {
|
|||
if fd <= 0 || len(iv) != d.ivLen {
|
||||
log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
|
||||
}
|
||||
d.dbg("dirCache.Lookup %p hit fd=%d dup=%d iv=%x\n", node, e.fd, fd, iv)
|
||||
return fd, iv
|
||||
}
|
||||
|
||||
|
@ -153,30 +140,15 @@ func (d *dirCache) expireThread() {
|
|||
for {
|
||||
time.Sleep(60 * time.Second)
|
||||
d.Clear()
|
||||
d.stats()
|
||||
}
|
||||
}
|
||||
|
||||
// stats prints hit rate statistics and resets the counters. No-op if
|
||||
// enableStats == false.
|
||||
func (d *dirCache) stats() {
|
||||
if !enableStats {
|
||||
return
|
||||
}
|
||||
d.Lock()
|
||||
lookups := d.lookups
|
||||
hits := d.hits
|
||||
d.lookups = 0
|
||||
d.hits = 0
|
||||
d.Unlock()
|
||||
if lookups > 0 {
|
||||
fmt.Printf("dirCache: hits=%3d lookups=%3d, rate=%3d%%\n", hits, lookups, (hits*100)/lookups)
|
||||
}
|
||||
}
|
||||
|
||||
// dbg prints a debug message. Usually disabled.
|
||||
func (d *dirCache) dbg(format string, a ...interface{}) {
|
||||
if enableDebugMessages {
|
||||
fmt.Printf(format, a...)
|
||||
func (d* dirCache) Delete(path string) {
|
||||
for i := range d.entries {
|
||||
e := &d.entries[i]
|
||||
if e.path == path {
|
||||
e.Clear()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"libgocryptfs/v2/allocator"
|
||||
"libgocryptfs/v2/internal/configfile"
|
||||
"libgocryptfs/v2/internal/cryptocore"
|
||||
"libgocryptfs/v2/internal/nametransform"
|
||||
"libgocryptfs/v2/internal/syscallcompat"
|
||||
)
|
||||
|
||||
func (volume *Volume) 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.
|
||||
volume.dirIVLock.Lock()
|
||||
defer volume.dirIVLock.Unlock()
|
||||
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) {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return nil, nil, 0
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
parentDirFd, cDirName, err := volume.prepareAtSyscallMyself(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 !volume.plainTextNames {
|
||||
// Read the DirIV from disk
|
||||
cachedIV, err = volume.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 volume.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 := volume.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 {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
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 = volume.nameTransform.WriteLongNameAt(dirfd, cName, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Create directory
|
||||
err = volume.mkdirWithIv(dirfd, cName, mode)
|
||||
if err != nil {
|
||||
nametransform.DeleteLongNameAt(dirfd, cName)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
err = volume.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 {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
parentDirFd, cName, err := volume.prepareAtSyscall(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())
|
||||
// The directory is in an inconsistent state between rename and rmdir.
|
||||
// Protect against concurrent readers.
|
||||
volume.dirIVLock.Lock()
|
||||
defer volume.dirIVLock.Unlock()
|
||||
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)
|
||||
}
|
||||
volume.dirCache.Delete(relPath)
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,536 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"C"
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"libgocryptfs/v2/internal/contentenc"
|
||||
"libgocryptfs/v2/internal/nametransform"
|
||||
"libgocryptfs/v2/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(fd int, cName, path string) int {
|
||||
volume.handlesLock.Lock()
|
||||
c := 0
|
||||
for {
|
||||
_, ok := volume.fileHandles[c]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
c++
|
||||
}
|
||||
volume.fileHandles[c] = &File {
|
||||
fd: os.NewFile(uintptr(fd), cName),
|
||||
path: string([]byte(path[:])),
|
||||
}
|
||||
volume.handlesLock.Unlock()
|
||||
return c
|
||||
}
|
||||
|
||||
// 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(f *File, dst []byte, off uint64, length uint64) ([]byte, bool) {
|
||||
fd := f.fd
|
||||
// Get the file ID, either from the open file table, or from disk.
|
||||
var fileID []byte
|
||||
if f.ID != nil {
|
||||
// Use the cached value in the file table
|
||||
fileID = f.ID
|
||||
} 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
|
||||
f.ID = 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) {
|
||||
volume.handlesLock.RLock()
|
||||
f := volume.fileHandles[handleID]
|
||||
volume.handlesLock.RUnlock()
|
||||
fd := f.fd
|
||||
fileWasEmpty := false
|
||||
var fileID []byte
|
||||
if f.ID != nil {
|
||||
fileID = f.ID
|
||||
} 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
|
||||
}
|
||||
f.ID = 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(f, 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 {
|
||||
volume.handlesLock.RLock()
|
||||
f := volume.fileHandles[handleID]
|
||||
volume.handlesLock.RUnlock()
|
||||
// The file was empty, so it did not have a header. Create one.
|
||||
if oldPlainSz == 0 {
|
||||
id, err := createHeader(f.fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
f.ID = id
|
||||
}
|
||||
cSz := int64(volume.contentEnc.PlainSizeToCipherSize(newPlainSz))
|
||||
err := syscall.Ftruncate(int(f.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 {
|
||||
volume.handlesLock.RLock()
|
||||
f := volume.fileHandles[handleID]
|
||||
volume.handlesLock.RUnlock()
|
||||
fileFD := int(f.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, f.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(f, 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 {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
dirfd, cName, err := volume.prepareAtSyscallMyself(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(fd, cName, path)
|
||||
}
|
||||
|
||||
//export gcf_open_write_mode
|
||||
func gcf_open_write_mode(sessionID int, path string, mode uint32) int {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
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(fd, cName, path)
|
||||
}
|
||||
|
||||
//export gcf_truncate
|
||||
func gcf_truncate(sessionID int, path string, offset uint64) bool {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
for handleID, file := range volume.fileHandles {
|
||||
if file.path == path {
|
||||
return volume.truncate(handleID, offset)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//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
|
||||
}
|
||||
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
|
||||
volume.handlesLock.RLock()
|
||||
f := volume.fileHandles[handleID]
|
||||
volume.handlesLock.RUnlock()
|
||||
f.fdLock.RLock()
|
||||
defer f.fdLock.RUnlock()
|
||||
f.contentLock.RLock()
|
||||
defer f.contentLock.RUnlock()
|
||||
out, success := volume.doRead(f, 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
|
||||
}
|
||||
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
|
||||
volume.handlesLock.RLock()
|
||||
f := volume.fileHandles[handleID]
|
||||
volume.handlesLock.RUnlock()
|
||||
f.fdLock.RLock()
|
||||
defer f.fdLock.RUnlock()
|
||||
f.contentLock.Lock()
|
||||
defer f.contentLock.Unlock()
|
||||
n, _ := volume.doWrite(handleID, data, offset)
|
||||
return n
|
||||
}
|
||||
|
||||
//export gcf_close_file
|
||||
func gcf_close_file(sessionID, handleID int) {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
volume.handlesLock.Lock()
|
||||
f := volume.fileHandles[handleID]
|
||||
f.fdLock.Lock()
|
||||
f.fd.Close()
|
||||
delete(volume.fileHandles, handleID)
|
||||
volume.handlesLock.Unlock()
|
||||
f.fdLock.Unlock()
|
||||
}
|
||||
|
||||
//export gcf_remove_file
|
||||
func gcf_remove_file(sessionID int, path string) bool {
|
||||
value, ok := OpenedVolumes.Load(sessionID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
volume := value.(*Volume)
|
||||
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)
|
||||
}
|
334
fsck.go
334
fsck.go
|
@ -1,334 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/fusefrontend"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
|
||||
"github.com/rfjakob/gocryptfs/v2/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
|
||||
}
|
||||
// Sort alphabetically to make fsck runs deterministic
|
||||
sort.Strings(entries)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
16
go.mod
16
go.mod
|
@ -1,16 +1,10 @@
|
|||
module github.com/rfjakob/gocryptfs/v2
|
||||
module libgocryptfs/v2
|
||||
|
||||
go 1.16
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/aperturerobotics/jacobsa-crypto v1.0.0
|
||||
github.com/hanwen/go-fuse/v2 v2.3.0
|
||||
github.com/moby/sys/mountinfo v0.6.2
|
||||
github.com/pkg/xattr v0.4.3
|
||||
github.com/aperturerobotics/jacobsa-crypto v1.0.1
|
||||
github.com/rfjakob/eme v1.1.2
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41
|
||||
)
|
||||
|
|
52
go.sum
52
go.sum
|
@ -1,53 +1,13 @@
|
|||
github.com/aperturerobotics/jacobsa-crypto v0.0.0-20190317225127-9f44e2d11115/go.mod h1:XKd7k7LIBmeR/WGENaSpUSjQbWBVKZFhMT7+zKM5KVU=
|
||||
github.com/aperturerobotics/jacobsa-crypto v1.0.0 h1:ARfIuzgovK+5leAKbFHcicKEgMzD94tb/FTiWSHdGLU=
|
||||
github.com/aperturerobotics/jacobsa-crypto v1.0.0/go.mod h1:xq0oOkHSPQ1E5ByqbwLhCJ1mygYHtXTMQnvHD4tz4Cc=
|
||||
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/v2 v2.3.0 h1:t5ivNIH2PK+zw4OBul/iJjsoG9K6kXo4nMDoBpciC8A=
|
||||
github.com/hanwen/go-fuse/v2 v2.3.0/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
|
||||
github.com/aperturerobotics/jacobsa-crypto v1.0.1 h1:BsIgQFvT0uveYFe+0hc7SwSsCNNIPmxFjm9oi0qGdGM=
|
||||
github.com/aperturerobotics/jacobsa-crypto v1.0.1/go.mod h1:oR/7BV4/0QbjutdWNOQ2N0PxGPT9qFVDi4gw0UepxDA=
|
||||
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/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/pkg/xattr v0.4.3 h1:5Jx4GCg5ABtqWZH8WLzeI4fOtM1HyX4RBawuCoua1es=
|
||||
github.com/pkg/xattr v0.4.3/go.mod h1:sBD3RAqlr8Q+RC3FutZcikpT8nyDrIEEBw2J744gVWs=
|
||||
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.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4=
|
||||
github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f h1:8P2MkG70G76gnZBOPGwmMIgwBb/rESQuwsJ7K8ds4NE=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
gocryptfs-xray
|
|
@ -1,62 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/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; 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,221 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/fido2"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/readpassword"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||
)
|
||||
|
||||
// GitVersion is the gocryptfs version according to git, set by build.bash
|
||||
var GitVersion = "[GitVersion 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 (
|
||||
myName = "gocryptfs-xray"
|
||||
)
|
||||
|
||||
// blockSize is the ciphertext block size including overheads
|
||||
func blockSize(alg cryptocore.AEADTypeEnum) int {
|
||||
return alg.NonceSize + contentenc.DefaultBS + cryptocore.AuthTagLen
|
||||
}
|
||||
|
||||
func errExit(err error) {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func prettyPrintHeader(h *contentenc.FileHeader, algo cryptocore.AEADTypeEnum) {
|
||||
id := hex.EncodeToString(h.ID)
|
||||
fmt.Printf("Header: Version: %d, Id: %s, assuming %s mode\n", h.Version, id, algo.Algo)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type argContainer struct {
|
||||
dumpmasterkey *bool
|
||||
decryptPaths *bool
|
||||
encryptPaths *bool
|
||||
aessiv *bool
|
||||
xchacha *bool
|
||||
sep0 *bool
|
||||
fido2 *string
|
||||
version *bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
var args argContainer
|
||||
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.xchacha = flag.Bool("xchacha", false, "Assume XChaCha20-Poly1305 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)
|
||||
}
|
||||
f, err := os.Open(fn)
|
||||
if err != nil {
|
||||
errExit(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if *args.dumpmasterkey {
|
||||
dumpMasterKey(fn, *args.fido2)
|
||||
} else {
|
||||
inspectCiphertext(&args, f)
|
||||
}
|
||||
}
|
||||
|
||||
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, err = readpassword.Once(nil, nil, "")
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.ReadPassword)
|
||||
}
|
||||
}
|
||||
masterkey, err := cf.DecryptMasterKey(pw)
|
||||
// Purge password from memory
|
||||
for i := range pw {
|
||||
pw[i] = 0
|
||||
}
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.LoadConf)
|
||||
}
|
||||
fmt.Println(hex.EncodeToString(masterkey))
|
||||
// Purge masterkey from memory
|
||||
for i := range masterkey {
|
||||
masterkey[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func inspectCiphertext(args *argContainer, fd *os.File) {
|
||||
algo := cryptocore.BackendGoGCM
|
||||
if *args.aessiv {
|
||||
algo = cryptocore.BackendAESSIV
|
||||
} else if *args.xchacha {
|
||||
algo = cryptocore.BackendXChaCha20Poly1305
|
||||
}
|
||||
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, algo)
|
||||
var i int64
|
||||
buf := make([]byte, blockSize(algo))
|
||||
for i = 0; ; i++ {
|
||||
off := contentenc.HeaderLen + i*int64(blockSize(algo))
|
||||
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 < algo.NonceSize+cryptocore.AuthTagLen+1 {
|
||||
errExit(fmt.Errorf("corrupt block: truncated data, len=%d", n))
|
||||
}
|
||||
data := buf[:n]
|
||||
// Parse block data
|
||||
iv := data[:algo.NonceSize]
|
||||
tag := data[len(data)-cryptocore.AuthTagLen:]
|
||||
if *args.aessiv {
|
||||
tag = data[algo.NonceSize : algo.NonceSize+cryptocore.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-256 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-512 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/v2/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.Equal(out, expected) {
|
||||
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.Equal(out, expected) {
|
||||
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
|
59
help.go
59
help.go
|
@ -1,59 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/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(`
|
||||
Notes: All options can equivalently use "-" (single dash) or "--" (double dash).
|
||||
A standalone "--" stops option parsing.
|
||||
`)
|
||||
fmt.Printf("\nOptions:\n")
|
||||
flagSet.PrintDefaults()
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"libgocryptfs/v2/internal/configfile"
|
||||
"libgocryptfs/v2/internal/syscallcompat"
|
||||
)
|
||||
|
||||
func getParentPath(path string) string {
|
||||
parent := filepath.Dir(path)
|
||||
if parent == "." {
|
||||
return ""
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
// 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) prepareAtSyscall(path string) (dirfd int, cName string, err error) {
|
||||
if path == "/" {
|
||||
return volume.prepareAtSyscallMyself(path)
|
||||
}
|
||||
|
||||
if volume.isFiltered(path) {
|
||||
return -1, "", nil
|
||||
}
|
||||
|
||||
var encryptName func(int, string, []byte) (string, error)
|
||||
if !volume.plainTextNames {
|
||||
encryptName = func(dirfd int, child string, iv []byte) (cName string, err error) {
|
||||
// Badname allowed, try to determine filenames
|
||||
if volume.nameTransform.HaveBadnamePatterns() {
|
||||
return volume.nameTransform.EncryptAndHashBadName(child, iv, dirfd)
|
||||
}
|
||||
return volume.nameTransform.EncryptAndHashName(child, iv)
|
||||
}
|
||||
}
|
||||
|
||||
child := filepath.Base(path)
|
||||
parentPath := getParentPath(path)
|
||||
|
||||
// Cache lookup
|
||||
var iv []byte
|
||||
dirfd, iv = volume.dirCache.Lookup(parentPath)
|
||||
if dirfd > 0 {
|
||||
if volume.plainTextNames {
|
||||
return dirfd, child, nil
|
||||
}
|
||||
var err error
|
||||
cName, err = encryptName(dirfd, child, iv)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
return dirfd, cName, nil
|
||||
}
|
||||
|
||||
// Slowpath: Open ourselves & read diriv
|
||||
parentDirfd, myCName, err := volume.prepareAtSyscallMyself(parentPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer syscall.Close(parentDirfd)
|
||||
|
||||
dirfd, err = syscallcompat.Openat(parentDirfd, myCName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
// Cache store
|
||||
if !volume.plainTextNames {
|
||||
var err error
|
||||
iv, err = volume.nameTransform.ReadDirIVAt(dirfd)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
}
|
||||
volume.dirCache.Store(parentPath, dirfd, iv)
|
||||
|
||||
if volume.plainTextNames {
|
||||
return dirfd, child, nil
|
||||
}
|
||||
|
||||
cName, err = encryptName(dirfd, child, iv)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (volume *Volume) prepareAtSyscallMyself(path string) (dirfd int, cName string, err error) {
|
||||
dirfd = -1
|
||||
|
||||
// Handle root node
|
||||
if path == "/" {
|
||||
var err error
|
||||
// Open cipherdir (following symlinks)
|
||||
dirfd, err = syscallcompat.Open(volume.rootCipherDir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
return dirfd, ".", nil
|
||||
}
|
||||
|
||||
// Otherwise convert to prepareAtSyscall of parent node
|
||||
return volume.prepareAtSyscall(path)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
size := uint64(st.Size)
|
||||
if isRegular(st.Mode) {
|
||||
size = volume.contentEnc.CipherSizeToPlainSize(uint64(st.Size))
|
||||
} else if isSymlink(st.Mode) {
|
||||
// read and decrypt target
|
||||
target := volume.readlink(dirfd, cName)
|
||||
size = uint64(len(target))
|
||||
}
|
||||
return size
|
||||
}
|
30
info.go
30
info.go
|
@ -1,30 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
cf, err := configfile.Load(filename)
|
||||
if err != nil {
|
||||
fmt.Printf("Loading config file failed: %v\n", err)
|
||||
os.Exit(exitcodes.LoadConf)
|
||||
}
|
||||
s := cf.ScryptObject
|
||||
algo, _ := cf.ContentEncryption()
|
||||
// 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))
|
||||
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)
|
||||
fmt.Printf("contentEncryption: %s\n", algo.Algo) // lowercase because not in JSON
|
||||
}
|
155
init_dir.go
155
init_dir.go
|
@ -1,155 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/fido2"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/readpassword"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
|
||||
"github.com/rfjakob/gocryptfs/v2/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)
|
||||
}
|
||||
if !args.xchacha && !stupidgcm.CpuHasAES() {
|
||||
tlog.Info.Printf(tlog.ColorYellow +
|
||||
"Notice: Your CPU does not have AES acceleration. Consider using -xchacha for better performance." +
|
||||
tlog.ColorReset)
|
||||
}
|
||||
}
|
||||
// Choose password for config file
|
||||
if len(args.extpass) == 0 && 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, err = readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
||||
if err != nil {
|
||||
tlog.Fatal.Println(err)
|
||||
os.Exit(exitcodes.ReadPassword)
|
||||
}
|
||||
fido2CredentialID = nil
|
||||
fido2HmacSalt = nil
|
||||
}
|
||||
creator := tlog.ProgramName + " " + GitVersion
|
||||
err = configfile.Create(&configfile.CreateArgs{
|
||||
Filename: args.config,
|
||||
Password: password,
|
||||
PlaintextNames: args.plaintextnames,
|
||||
LogN: args.scryptn,
|
||||
Creator: creator,
|
||||
AESSIV: args.aessiv,
|
||||
Fido2CredentialID: fido2CredentialID,
|
||||
Fido2HmacSalt: fido2HmacSalt,
|
||||
DeterministicNames: args.deterministic_names,
|
||||
XChaCha20Poly1305: args.xchacha,
|
||||
LongNameMax: args.longnamemax,
|
||||
})
|
||||
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 && !args.deterministic_names {
|
||||
// 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)
|
||||
}
|
|
@ -10,10 +10,9 @@ import (
|
|||
|
||||
"os"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||
"libgocryptfs/v2/internal/contentenc"
|
||||
"libgocryptfs/v2/internal/cryptocore"
|
||||
"libgocryptfs/v2/internal/exitcodes"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -69,8 +68,6 @@ type CreateArgs struct {
|
|||
LogN int
|
||||
Creator string
|
||||
AESSIV bool
|
||||
Fido2CredentialID []byte
|
||||
Fido2HmacSalt []byte
|
||||
DeterministicNames bool
|
||||
XChaCha20Poly1305 bool
|
||||
LongNameMax uint8
|
||||
|
@ -79,7 +76,7 @@ type CreateArgs struct {
|
|||
// Create - create a new config with a random key encrypted with
|
||||
// "Password" and write it to "Filename".
|
||||
// Uses scrypt with cost parameter "LogN".
|
||||
func Create(args *CreateArgs) error {
|
||||
func Create(args *CreateArgs, returnedScryptHashBuff []byte) error {
|
||||
cf := ConfFile{
|
||||
filename: args.Filename,
|
||||
Creator: args.Creator,
|
||||
|
@ -113,12 +110,10 @@ func Create(args *CreateArgs) error {
|
|||
if args.AESSIV {
|
||||
cf.setFeatureFlag(FlagAESSIV)
|
||||
}
|
||||
if len(args.Fido2CredentialID) > 0 {
|
||||
cf.setFeatureFlag(FlagFIDO2)
|
||||
cf.FIDO2 = &FIDO2Params{
|
||||
CredentialID: args.Fido2CredentialID,
|
||||
HMACSalt: args.Fido2HmacSalt,
|
||||
}
|
||||
// Catch bugs and invalid cli flag combinations early
|
||||
cf.ScryptObject = NewScryptKDF(args.LogN)
|
||||
if err := cf.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Catch bugs and invalid cli flag combinations early
|
||||
cf.ScryptObject = NewScryptKDF(args.LogN)
|
||||
|
@ -128,14 +123,17 @@ func Create(args *CreateArgs) error {
|
|||
{
|
||||
// Generate new random master key
|
||||
key := cryptocore.RandBytes(cryptocore.KeyLen)
|
||||
tlog.PrintMasterkeyReminder(key)
|
||||
// Encrypt it using the password
|
||||
// This sets ScryptObject and EncryptedKey
|
||||
// Note: this looks at the FeatureFlags, so call it AFTER setting them.
|
||||
cf.EncryptKey(key, args.Password, args.LogN)
|
||||
scryptHash := cf.EncryptKey(key, args.Password, args.LogN, len(returnedScryptHashBuff) > 0)
|
||||
for i := range key {
|
||||
key[i] = 0
|
||||
}
|
||||
for i := range scryptHash {
|
||||
returnedScryptHashBuff[i] = scryptHash[i]
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
// key runs out of scope here
|
||||
}
|
||||
// Write file to disk
|
||||
|
@ -162,7 +160,7 @@ func LoadAndDecrypt(filename string, password []byte) ([]byte, *ConfFile, error)
|
|||
}
|
||||
|
||||
// Decrypt the masterkey using the password
|
||||
key, err := cf.DecryptMasterKey(password)
|
||||
key, _, err := cf.DecryptMasterKey(password, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -187,7 +185,6 @@ func Load(filename string) (*ConfFile, error) {
|
|||
// Unmarshal
|
||||
err = json.Unmarshal(js, &cf)
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("Failed to unmarshal config file")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -207,40 +204,48 @@ func (cf *ConfFile) setFeatureFlag(flag flagIota) {
|
|||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[flag])
|
||||
}
|
||||
|
||||
// DecryptMasterKey decrypts the masterkey stored in cf.EncryptedKey using
|
||||
// password.
|
||||
func (cf *ConfFile) DecryptMasterKey(password []byte) (masterkey []byte, err error) {
|
||||
// Generate derived key from password
|
||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||
|
||||
// Unlock master key using password-based key
|
||||
// libgocryptfs function to allow masterkey to be directely decrypted using the scrypt hash
|
||||
func (cf *ConfFile) DecryptMasterKeyWithScryptHash(scryptHash []byte) ([]byte, error) {
|
||||
useHKDF := cf.IsFeatureFlagSet(FlagHKDF)
|
||||
ce := getKeyEncrypter(scryptHash, useHKDF)
|
||||
|
||||
tlog.Warn.Enabled = false // Silence DecryptBlock() error messages on incorrect password
|
||||
masterkey, err = ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
tlog.Warn.Enabled = true
|
||||
masterkey, err := ce.DecryptBlock(cf.EncryptedKey, 0, nil)
|
||||
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
scryptHash = nil
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("failed to unlock master key: %s", err.Error())
|
||||
return nil, exitcodes.NewErr("Password incorrect.", exitcodes.PasswordIncorrect)
|
||||
}
|
||||
|
||||
return masterkey, nil
|
||||
}
|
||||
|
||||
// DecryptMasterKey decrypts the masterkey stored in cf.EncryptedKey using
|
||||
// password.
|
||||
func (cf *ConfFile) DecryptMasterKey(password []byte, giveHash bool) (masterkey, scryptHash []byte, err error) {
|
||||
// Generate derived key from password
|
||||
scryptHash = cf.ScryptObject.DeriveKey(password)
|
||||
|
||||
// Unlock master key using password-based key
|
||||
masterkey, err = cf.DecryptMasterKeyWithScryptHash(scryptHash)
|
||||
|
||||
if !giveHash {
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
scryptHash = nil
|
||||
}
|
||||
|
||||
return masterkey, scryptHash, err
|
||||
}
|
||||
|
||||
// EncryptKey - encrypt "key" using an scrypt hash generated from "password"
|
||||
// and store it in cf.EncryptedKey.
|
||||
// Uses scrypt with cost parameter logN and stores the scrypt parameters in
|
||||
// cf.ScryptObject.
|
||||
func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int) {
|
||||
func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int, giveHash bool) []byte {
|
||||
// Generate scrypt-derived key from password
|
||||
cf.ScryptObject = NewScryptKDF(logN)
|
||||
scryptHash := cf.ScryptObject.DeriveKey(password)
|
||||
|
@ -250,13 +255,34 @@ func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int) {
|
|||
ce := getKeyEncrypter(scryptHash, useHKDF)
|
||||
cf.EncryptedKey = ce.EncryptBlock(key, 0, nil)
|
||||
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
if !giveHash {
|
||||
// Purge scrypt-derived key
|
||||
for i := range scryptHash {
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
scryptHash = nil
|
||||
}
|
||||
scryptHash = nil
|
||||
ce.Wipe()
|
||||
ce = nil
|
||||
|
||||
return scryptHash
|
||||
}
|
||||
|
||||
func (cf *ConfFile) GetMasterkey(password, givenScryptHash, returnedScryptHashBuff []byte) ([]byte, error) {
|
||||
var masterkey []byte
|
||||
var err error
|
||||
if len(givenScryptHash) > 0 { //decrypt with hash
|
||||
masterkey, err = cf.DecryptMasterKeyWithScryptHash(givenScryptHash)
|
||||
} else { //decrypt with password
|
||||
var scryptHash []byte
|
||||
masterkey, scryptHash, err = cf.DecryptMasterKey(password, len(returnedScryptHashBuff) > 0)
|
||||
//copy and wipe scryptHash
|
||||
for i := range scryptHash {
|
||||
returnedScryptHashBuff[i] = scryptHash[i]
|
||||
scryptHash[i] = 0
|
||||
}
|
||||
}
|
||||
return masterkey, err
|
||||
}
|
||||
|
||||
// WriteFile - write out config in JSON format to file "filename.tmp"
|
||||
|
@ -286,7 +312,6 @@ func (cf *ConfFile) WriteFile() error {
|
|||
if err != nil {
|
||||
// This can happen on network drives: FRITZ.NAS mounted on MacOS returns
|
||||
// "operation not supported": https://github.com/rfjakob/gocryptfs/issues/390
|
||||
tlog.Warn.Printf("Warning: fsync failed: %v", err)
|
||||
// Try sync instead
|
||||
syscall.Sync()
|
||||
}
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
package configfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/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(&CreateArgs{
|
||||
Filename: "config_test/tmp.conf",
|
||||
Password: testPw,
|
||||
LogN: 10,
|
||||
Creator: "test"})
|
||||
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 TestCreateConfPlaintextnames(t *testing.T) {
|
||||
err := Create(&CreateArgs{
|
||||
Filename: "config_test/tmp.conf",
|
||||
Password: testPw,
|
||||
PlaintextNames: true,
|
||||
LogN: 10,
|
||||
Creator: "test"})
|
||||
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(&CreateArgs{
|
||||
Filename: "config_test/tmp.conf",
|
||||
Password: testPw,
|
||||
LogN: 10,
|
||||
Creator: "test",
|
||||
AESSIV: true})
|
||||
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 TestCreateConfLongNameMax(t *testing.T) {
|
||||
args := &CreateArgs{
|
||||
Filename: "config_test/tmp.conf",
|
||||
Password: testPw,
|
||||
LogN: 10,
|
||||
Creator: "test",
|
||||
LongNameMax: 100,
|
||||
}
|
||||
err := Create(args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, c, err := LoadAndDecrypt("config_test/tmp.conf", testPw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !c.IsFeatureFlagSet(FlagLongNameMax) {
|
||||
t.Error("FlagLongNameMax should be set but is not")
|
||||
}
|
||||
if c.LongNameMax != args.LongNameMax {
|
||||
t.Errorf("wrong LongNameMax value: want=%d have=%d", args.LongNameMax, c.LongNameMax)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFeatureFlagKnown(t *testing.T) {
|
||||
// Test a few hardcoded values
|
||||
testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames", "AESSIV"}
|
||||
// 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)
|
||||
}
|
||||
|
||||
for _, f := range testKnownFlags {
|
||||
if !isFeatureFlagKnown(f) {
|
||||
t.Errorf("flag %q should be known", f)
|
||||
}
|
||||
}
|
||||
|
||||
f := "StrangeFeatureFlag"
|
||||
if isFeatureFlagKnown(f) {
|
||||
t.Errorf("flag %q should be NOT known", f)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -8,9 +8,8 @@ import (
|
|||
|
||||
"golang.org/x/crypto/scrypt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||
"libgocryptfs/v2/internal/cryptocore"
|
||||
"libgocryptfs/v2/internal/exitcodes"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -64,7 +63,6 @@ func NewScryptKDF(logN int) ScryptKDF {
|
|||
// DeriveKey returns a new key from a supplied password.
|
||||
func (s *ScryptKDF) DeriveKey(pw []byte) []byte {
|
||||
if err := s.validateParams(); err != nil {
|
||||
tlog.Fatal.Println(err.Error())
|
||||
os.Exit(exitcodes.ScryptParams)
|
||||
}
|
||||
k, err := scrypt.Key(pw, s.Salt, s.N, s.R, s.P, s.KeyLen)
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
package configfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
$ time go test -bench . -run none
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/rfjakob/gocryptfs/v2/internal/configfile
|
||||
cpu: Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz
|
||||
BenchmarkScryptN/10-4 339 3488649 ns/op 1053167 B/op 22 allocs/op ... 3ms+1MiB
|
||||
BenchmarkScryptN/11-4 175 6816072 ns/op 2101742 B/op 22 allocs/op
|
||||
BenchmarkScryptN/12-4 87 13659346 ns/op 4198898 B/op 22 allocs/op
|
||||
BenchmarkScryptN/13-4 43 27443071 ns/op 8393209 B/op 22 allocs/op
|
||||
BenchmarkScryptN/14-4 21 56931664 ns/op 16781820 B/op 22 allocs/op
|
||||
BenchmarkScryptN/15-4 10 108494502 ns/op 33559027 B/op 22 allocs/op
|
||||
BenchmarkScryptN/16-4 5 217347137 ns/op 67113465 B/op 22 allocs/op ... 217ms+67MiB
|
||||
BenchmarkScryptN/17-4 3 449680138 ns/op 134222362 B/op 22 allocs/op
|
||||
BenchmarkScryptN/18-4 2 867481653 ns/op 268440064 B/op 22 allocs/op
|
||||
BenchmarkScryptN/19-4 1 1738085333 ns/op 536875536 B/op 23 allocs/op
|
||||
BenchmarkScryptN/20-4 1 3508224867 ns/op 1073746448 B/op 23 allocs/op
|
||||
BenchmarkScryptN/21-4 1 9536561994 ns/op 2147488272 B/op 23 allocs/op
|
||||
BenchmarkScryptN/22-4 1 16937072495 ns/op 4294971920 B/op 23 allocs/op
|
||||
PASS
|
||||
ok github.com/rfjakob/gocryptfs/v2/internal/configfile 47.545s
|
||||
*/
|
||||
|
||||
func BenchmarkScryptN(b *testing.B) {
|
||||
for n := 10; n <= 20; n++ {
|
||||
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
|
||||
benchmarkScryptN(b, n)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkScryptN(b *testing.B, n int) {
|
||||
kdf := NewScryptKDF(n)
|
||||
for i := 0; i < b.N; i++ {
|
||||
kdf.DeriveKey(testPw)
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
|
@ -3,7 +3,7 @@ package configfile
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
|
||||
"libgocryptfs/v2/internal/contentenc"
|
||||
)
|
||||
|
||||
// Validate that the combination of settings makes sense and is supported
|
||||
|
|
|
@ -4,19 +4,18 @@ package contentenc
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||
"libgocryptfs/v2/internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
//value from FUSE doc
|
||||
MAX_KERNEL_WRITE = 128 * 1024
|
||||
|
||||
// DefaultBS is the default plaintext block size
|
||||
DefaultBS = 4096
|
||||
// DefaultIVBits is the default length of IV, in bits.
|
||||
|
@ -58,19 +57,17 @@ type ContentEnc struct {
|
|||
|
||||
// New returns an initialized ContentEnc instance.
|
||||
func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
|
||||
tlog.Debug.Printf("contentenc.New: plainBS=%d", plainBS)
|
||||
|
||||
if fuse.MAX_KERNEL_WRITE%plainBS != 0 {
|
||||
log.Panicf("unaligned MAX_KERNEL_WRITE=%d", fuse.MAX_KERNEL_WRITE)
|
||||
if MAX_KERNEL_WRITE%plainBS != 0 {
|
||||
log.Panicf("unaligned MAX_KERNEL_WRITE=%d", MAX_KERNEL_WRITE)
|
||||
}
|
||||
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
|
||||
// Take IV and GHASH overhead into account.
|
||||
cReqSize := int(fuse.MAX_KERNEL_WRITE / plainBS * cipherBS)
|
||||
cReqSize := int(MAX_KERNEL_WRITE / plainBS * cipherBS)
|
||||
// Unaligned reads (happens during fsck, could also happen with O_DIRECT?)
|
||||
// touch one additional ciphertext and plaintext block. Reserve space for the
|
||||
// extra block.
|
||||
cReqSize += int(cipherBS)
|
||||
pReqSize := fuse.MAX_KERNEL_WRITE + int(plainBS)
|
||||
pReqSize := MAX_KERNEL_WRITE + int(plainBS)
|
||||
c := &ContentEnc{
|
||||
cryptoCore: cc,
|
||||
plainBS: plainBS,
|
||||
|
@ -145,12 +142,10 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
|||
|
||||
// All-zero block?
|
||||
if bytes.Equal(ciphertext, be.allZeroBlock) {
|
||||
tlog.Debug.Printf("DecryptBlock: file hole encountered")
|
||||
return make([]byte, be.plainBS), nil
|
||||
}
|
||||
|
||||
if len(ciphertext) < be.cryptoCore.IVLen {
|
||||
tlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext))
|
||||
return nil, errors.New("Block is too short")
|
||||
}
|
||||
|
||||
|
@ -162,7 +157,6 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
|||
// http://www.spinics.net/lists/kernel/msg2370127.html
|
||||
return nil, errors.New("all-zero nonce")
|
||||
}
|
||||
ciphertextOrig := ciphertext
|
||||
ciphertext = ciphertext[be.cryptoCore.IVLen:]
|
||||
|
||||
// Decrypt
|
||||
|
@ -172,8 +166,6 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
|||
plaintext, err := be.cryptoCore.AEADCipher.Open(plaintext, nonce, ciphertext, aData)
|
||||
|
||||
if err != nil {
|
||||
tlog.Debug.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig))
|
||||
tlog.Debug.Println(hex.Dump(ciphertextOrig))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
package contentenc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
)
|
||||
|
||||
type testRange struct {
|
||||
offset uint64
|
||||
length uint64
|
||||
}
|
||||
|
||||
func TestSplitRange(t *testing.T) {
|
||||
ranges := []testRange{
|
||||
{0, 70000},
|
||||
{0, 10},
|
||||
{234, 6511},
|
||||
{65444, 54},
|
||||
{0, 1024 * 1024},
|
||||
{0, 65536},
|
||||
{6654, 8945},
|
||||
}
|
||||
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
|
||||
f := New(cc, DefaultBS)
|
||||
|
||||
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) {
|
||||
ranges := []testRange{
|
||||
{0, 70000},
|
||||
{0, 10},
|
||||
{234, 6511},
|
||||
{65444, 54},
|
||||
{6654, 8945},
|
||||
}
|
||||
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
|
||||
f := New(cc, DefaultBS)
|
||||
|
||||
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)
|
||||
f := New(cc, DefaultBS)
|
||||
|
||||
b := f.CipherOffToBlockNo(788)
|
||||
if b != 0 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.CipherOffToBlockNo(HeaderLen + f.cipherBS)
|
||||
if b != 1 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.PlainOffToBlockNo(788)
|
||||
if b != 0 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
b = f.PlainOffToBlockNo(f.plainBS)
|
||||
if b != 1 {
|
||||
t.Errorf("actual: %d", b)
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
|
||||
"libgocryptfs/v2/internal/cryptocore"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -2,8 +2,6 @@ package contentenc
|
|||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||
)
|
||||
|
||||
// Contentenc methods that translate offsets between ciphertext and plaintext
|
||||
|
@ -44,12 +42,10 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
|||
|
||||
if cipherSize == HeaderLen {
|
||||
// This can happen between createHeader() and Write() and is harmless.
|
||||
tlog.Debug.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize)
|
||||
return 0
|
||||
}
|
||||
|
||||
if cipherSize < HeaderLen {
|
||||
tlog.Warn.Printf("cipherSize %d < header size %d: corrupt file\n", cipherSize, HeaderLen)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -58,7 +54,6 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
|||
lastBlockSize := (cipherSize - HeaderLen) % be.cipherBS
|
||||
if lastBlockSize > 0 && lastBlockSize <= be.BlockOverhead() {
|
||||
tmp := cipherSize - lastBlockSize + be.BlockOverhead() + 1
|
||||
tlog.Warn.Printf("cipherSize %d: incomplete last block (%d bytes), padding to %d bytes", cipherSize, lastBlockSize, tmp)
|
||||
cipherSize = tmp
|
||||
}
|
||||
|
||||
|
@ -69,7 +64,6 @@ func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
|
|||
overhead := be.BlockOverhead()*blockCount + HeaderLen
|
||||
|
||||
if overhead > cipherSize {
|
||||
tlog.Warn.Printf("cipherSize %d < overhead %d: corrupt file\n", cipherSize, overhead)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
package contentenc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/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)
|
||||
ce := New(cc, DefaultBS)
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
|
@ -13,9 +13,8 @@ import (
|
|||
|
||||
"github.com/rfjakob/eme"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/siv_aead"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
|
||||
"libgocryptfs/v2/internal/siv_aead"
|
||||
"libgocryptfs/v2/internal/stupidgcm"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -82,9 +81,6 @@ type CryptoCore struct {
|
|||
// Note: "key" is either the scrypt hash of the password (when decrypting
|
||||
// a config file) or the masterkey (when finally mounting the filesystem).
|
||||
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool) *CryptoCore {
|
||||
tlog.Debug.Printf("cryptocore.New: key=%d bytes, aeadType=%v, IVBitLen=%d, useHKDF=%v",
|
||||
len(key), aeadType, IVBitLen, useHKDF)
|
||||
|
||||
if len(key) != KeyLen {
|
||||
log.Panicf("Unsupported key length of %d bytes", len(key))
|
||||
}
|
||||
|
@ -213,13 +209,10 @@ type wiper interface {
|
|||
func (c *CryptoCore) Wipe() {
|
||||
be := c.AEADBackend
|
||||
if be == BackendOpenSSL || be == BackendAESSIV {
|
||||
tlog.Debug.Printf("CryptoCore.Wipe: Wiping AEADBackend %q key", be)
|
||||
// We don't use "x, ok :=" because we *want* to crash loudly if the
|
||||
// type assertion fails.
|
||||
w := c.AEADCipher.(wiper)
|
||||
w.Wipe()
|
||||
} else {
|
||||
tlog.Debug.Printf("CryptoCore.Wipe: Only nil'ing stdlib refs")
|
||||
}
|
||||
// We have no access to the keys (or key-equivalents) stored inside the
|
||||
// Go stdlib. Best we can is to nil the references and force a GC.
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package cryptocore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/v2/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)
|
||||
if c.IVLen != 12 {
|
||||
t.Fail()
|
||||
}
|
||||
c = New(key, BackendGoGCM, 128, useHKDF)
|
||||
if c.IVLen != 16 {
|
||||
t.Fail()
|
||||
}
|
||||
if stupidgcm.BuiltWithoutOpenssl {
|
||||
continue
|
||||
}
|
||||
c = New(key, BackendOpenSSL, 128, useHKDF)
|
||||
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)
|
||||
}
|
|
@ -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,41 +0,0 @@
|
|||
//go:build go1.7
|
||||
// +build go1.7
|
||||
|
||||
// ^^^^^^^^^^^^ we use the "sub-benchmark" feature that was added in Go 1.7
|
||||
|
||||
package cryptocore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
The throughput 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue