Skip to content

Commit

Permalink
cli: drop -forcedecode flag
Browse files Browse the repository at this point in the history
The rewritten openssl backend does not support this flag anymore,
and it was inherently dangerour. Drop it (ignored for compatibility)
  • Loading branch information
rfjakob committed Sep 10, 2021
1 parent c974116 commit d023cd6
Show file tree
Hide file tree
Showing 17 changed files with 37 additions and 104 deletions.
15 changes: 1 addition & 14 deletions Documentation/MANPAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,21 +256,8 @@ of a case where this may be useful is a situation where content is stored on a
filesystem that doesn't properly support UNIX ownership and permissions.

#### -forcedecode
Force decode of encrypted files even if the integrity check fails, instead of
failing with an IO error. Warning messages are still printed to syslog if corrupted
files are encountered.
It can be useful to recover files from disks with bad sectors or other corrupted
media. It shall not be used if the origin of corruption is unknown, specially
if you want to run executable files.

For corrupted media, note that you probably want to use dd_rescue(1)
instead, which will recover all but the corrupted 4kB block.

This option makes no sense in reverse mode. It requires gocryptfs to be compiled with openssl
support and implies -openssl true. Because of this, it is not compatible with -aessiv,
that uses built-in Go crypto.

Setting this option forces the filesystem to read-only and noexec.
Obsolete and ignored on gocryptfs v2.2 and later.

#### -fsname string
Override the filesystem name (first column in df -T). Can also be
Expand Down
33 changes: 3 additions & 30 deletions cli_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type argContainer struct {
debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
plaintextnames, quiet, nosyslog, wpanic,
longnames, allow_other, reverse, aessiv, nonempty, raw64,
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
noprealloc, speed, hkdf, serialize_reads, hh, info,
sharedstorage, fsck, one_file_system, deterministic_names,
xchacha bool
// Mount options with opposites
Expand Down Expand Up @@ -172,8 +172,6 @@ func parseCliOpts(osArgs []string) (args argContainer) {
flagSet.BoolVar(&args.speed, "speed", false, "Run crypto speed test")
flagSet.BoolVar(&args.hkdf, "hkdf", true, "Use HKDF as an additional key derivation step")
flagSet.BoolVar(&args.serialize_reads, "serialize_reads", false, "Try to serialize read operations")
flagSet.BoolVar(&args.forcedecode, "forcedecode", false, "Force decode of files even if integrity check fails."+
" Requires gocryptfs to be compiled with openssl support and implies -openssl true")
flagSet.BoolVar(&args.hh, "hh", false, "Show this long help text")
flagSet.BoolVar(&args.info, "info", false, "Display information about CIPHERDIR")
flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer")
Expand Down Expand Up @@ -234,7 +232,8 @@ func parseCliOpts(osArgs []string) (args argContainer) {
{
var tmp bool
flagSet.BoolVar(&tmp, "nofail", false, "Ignored for /etc/fstab compatibility")
flagSet.BoolVar(&tmp, "devrandom", false, "Deprecated (ignored for compatibility)")
flagSet.BoolVar(&tmp, "devrandom", false, "Obsolete, ignored for compatibility")
flagSet.BoolVar(&tmp, "forcedecode", false, "Obsolete, ignored for compatibility")
}

// Actual parsing
Expand Down Expand Up @@ -265,32 +264,6 @@ func parseCliOpts(osArgs []string) (args argContainer) {
os.Exit(exitcodes.Usage)
}
}
// "-forcedecode" only works with openssl. Check compilation and command line parameters
if args.forcedecode {
if stupidgcm.BuiltWithoutOpenssl {
tlog.Fatal.Printf("The -forcedecode flag requires openssl support, but gocryptfs was compiled without it!")
os.Exit(exitcodes.Usage)
}
if args.aessiv {
tlog.Fatal.Printf("The -forcedecode and -aessiv flags are incompatible because they use different crypto libs (openssl vs native Go)")
os.Exit(exitcodes.Usage)
}
if args.reverse {
tlog.Fatal.Printf("The reverse mode and the -forcedecode option are not compatible")
os.Exit(exitcodes.Usage)
}
// Has the user explicitly disabled openssl using "-openssl=false/0"?
if !args.openssl && opensslAuto != "auto" {
tlog.Fatal.Printf("-forcedecode requires openssl, but is disabled via command-line option")
os.Exit(exitcodes.Usage)
}
args.openssl = true

// Try to make it harder for the user to shoot himself in the foot.
args.ro = true
args.allow_other = false
args.ko = "noexec"
}
if 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)
Expand Down
4 changes: 2 additions & 2 deletions internal/configfile/config_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ func getKeyEncrypter(scryptHash []byte, useHKDF bool) *contentenc.ContentEnc {
if useHKDF {
IVLen = contentenc.DefaultIVBits
}
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, IVLen, useHKDF, false)
ce := contentenc.New(cc, 4096, false)
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, IVLen, useHKDF)
ce := contentenc.New(cc, 4096)
return ce
}

Expand Down
18 changes: 3 additions & 15 deletions internal/contentenc/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"

"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
"github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)

Expand Down Expand Up @@ -41,8 +40,6 @@ type ContentEnc struct {
allZeroBlock []byte
// All-zero block of size IVBitLen/8, for fast compares
allZeroNonce []byte
// Force decode even if integrity check fails (openSSL only)
forceDecode bool

// Ciphertext block "sync.Pool" pool. Always returns cipherBS-sized byte
// slices (usually 4128 bytes).
Expand All @@ -60,9 +57,8 @@ type ContentEnc struct {
}

// New returns an initialized ContentEnc instance.
func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc {
tlog.Debug.Printf("contentenc.New: plainBS=%d, forceDecode=%v",
plainBS, forceDecode)
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)
Expand All @@ -81,7 +77,6 @@ func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEn
cipherBS: cipherBS,
allZeroBlock: make([]byte, cipherBS),
allZeroNonce: make([]byte, cc.IVLen),
forceDecode: forceDecode,
cBlockPool: newBPool(int(cipherBS)),
CReqPool: newBPool(cReqSize),
pBlockPool: newBPool(int(plainBS)),
Expand Down Expand Up @@ -111,11 +106,7 @@ func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, file
var pBlock []byte
pBlock, err = be.DecryptBlock(cBlock, blockNo, fileID)
if err != nil {
if be.forceDecode && err == stupidgcm.ErrAuth {
tlog.Warn.Printf("DecryptBlocks: authentication failure in block #%d, overridden by forcedecode", firstBlockNo)
} else {
break
}
break
}
pBuf.Write(pBlock)
be.pBlockPool.Put(pBlock)
Expand Down Expand Up @@ -183,9 +174,6 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
if err != nil {
tlog.Debug.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig))
tlog.Debug.Println(hex.Dump(ciphertextOrig))
if be.forceDecode && err == stupidgcm.ErrAuth {
return plaintext, err
}
return nil, err
}

Expand Down
12 changes: 6 additions & 6 deletions internal/contentenc/content_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ func TestSplitRange(t *testing.T) {
testRange{6654, 8945})

key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
f := New(cc, DefaultBS, false)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
f := New(cc, DefaultBS)

for _, r := range ranges {
parts := f.ExplodePlainRange(r.offset, r.length)
Expand All @@ -51,8 +51,8 @@ func TestCiphertextRange(t *testing.T) {
testRange{6654, 8945})

key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
f := New(cc, DefaultBS, false)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
f := New(cc, DefaultBS)

for _, r := range ranges {

Expand All @@ -74,8 +74,8 @@ func TestCiphertextRange(t *testing.T) {

func TestBlockNo(t *testing.T) {
key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
f := New(cc, DefaultBS, false)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
f := New(cc, DefaultBS)

b := f.CipherOffToBlockNo(788)
if b != 0 {
Expand Down
4 changes: 2 additions & 2 deletions internal/contentenc/offsets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
// TestSizeToSize tests CipherSizeToPlainSize and PlainSizeToCipherSize
func TestSizeToSize(t *testing.T) {
key := make([]byte, cryptocore.KeyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true, false)
ce := New(cc, DefaultBS, false)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
ce := New(cc, DefaultBS)

const rangeMax = 10000

Expand Down
8 changes: 4 additions & 4 deletions internal/cryptocore/cryptocore.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ 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, forceDecode bool) *CryptoCore {
tlog.Debug.Printf("cryptocore.New: key=%d bytes, aeadType=%v, IVBitLen=%d, useHKDF=%v, forceDecode=%v",
len(key), aeadType, IVBitLen, useHKDF, forceDecode)
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))
Expand Down Expand Up @@ -120,7 +120,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
if IVBitLen != 128 {
log.Panicf("stupidgcm only supports 128-bit IVs, you wanted %d", IVBitLen)
}
aeadCipher = stupidgcm.NewAES256GCM(gcmKey, forceDecode)
aeadCipher = stupidgcm.NewAES256GCM(gcmKey)
case BackendGoGCM:
goGcmBlockCipher, err := aes.NewCipher(gcmKey)
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions internal/cryptocore/cryptocore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import (
func TestCryptoCoreNew(t *testing.T) {
key := make([]byte, 32)
for _, useHKDF := range []bool{true, false} {
c := New(key, BackendGoGCM, 96, useHKDF, false)
c := New(key, BackendGoGCM, 96, useHKDF)
if c.IVLen != 12 {
t.Fail()
}
c = New(key, BackendGoGCM, 128, useHKDF, false)
c = New(key, BackendGoGCM, 128, useHKDF)
if c.IVLen != 16 {
t.Fail()
}
if stupidgcm.BuiltWithoutOpenssl {
continue
}
c = New(key, BackendOpenSSL, 128, useHKDF, false)
c = New(key, BackendOpenSSL, 128, useHKDF)
if c.IVLen != 16 {
t.Fail()
}
Expand All @@ -37,5 +37,5 @@ func TestNewPanic(t *testing.T) {
}()

key := make([]byte, 16)
New(key, BackendOpenSSL, 128, true, false)
New(key, BackendOpenSSL, 128, true)
}
2 changes: 0 additions & 2 deletions internal/fusefrontend/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ type Args struct {
ConfigCustom bool
// NoPrealloc disables automatic preallocation before writing
NoPrealloc bool
// Force decode even if integrity check fails (openSSL only)
ForceDecode bool
// Exclude is a list of paths to make inaccessible, starting match at
// the filesystem root
Exclude []string
Expand Down
14 changes: 3 additions & 11 deletions internal/fusefrontend/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/contentenc"
"github.com/rfjakob/gocryptfs/v2/internal/inomap"
"github.com/rfjakob/gocryptfs/v2/internal/openfiletable"
"github.com/rfjakob/gocryptfs/v2/internal/stupidgcm"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
Expand Down Expand Up @@ -208,16 +207,9 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Er
plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
f.rootNode.contentEnc.CReqPool.Put(ciphertext)
if err != nil {
if f.rootNode.args.ForceDecode && err == stupidgcm.ErrAuth {
// We do not have the information which block was corrupt here anymore,
// but DecryptBlocks() has already logged it anyway.
tlog.Warn.Printf("doRead %d: off=%d len=%d: returning corrupt data due to forcedecode",
f.qIno.Ino, off, length)
} else {
curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
tlog.Warn.Printf("doRead %d: corrupt block #%d: %v", f.qIno.Ino, curruptBlockNo, err)
return nil, syscall.EIO
}
curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
tlog.Warn.Printf("doRead %d: corrupt block #%d: %v", f.qIno.Ino, curruptBlockNo, err)
return nil, syscall.EIO
}

// Crop down to the relevant part
Expand Down
4 changes: 2 additions & 2 deletions internal/fusefrontend/xattr_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
func newTestFS(args Args) *RootNode {
// Init crypto backend
key := make([]byte, cryptocore.KeyLen)
cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true, false)
cEnc := contentenc.New(cCore, contentenc.DefaultBS, false)
cCore := cryptocore.New(key, cryptocore.BackendGoGCM, contentenc.DefaultIVBits, true)
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
n := nametransform.New(cCore.EMECipher, true, true, nil, false)
rn := NewRootNode(args, cEnc, n)
oneSec := time.Second
Expand Down
2 changes: 1 addition & 1 deletion internal/speed/speed.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func bStupidGCM(b *testing.B) {
if stupidgcm.BuiltWithoutOpenssl {
b.Skip("openssl has been disabled at compile-time")
}
bEncrypt(b, stupidgcm.NewAES256GCM(randBytes(32), false))
bEncrypt(b, stupidgcm.NewAES256GCM(randBytes(32)))
}

// bGoGCM benchmarks Go stdlib GCM
Expand Down
2 changes: 1 addition & 1 deletion internal/speed/speed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func BenchmarkStupidGCMDecrypt(b *testing.B) {
if stupidgcm.BuiltWithoutOpenssl {
b.Skip("openssl has been disabled at compile-time")
}
bDecrypt(b, stupidgcm.NewAES256GCM(randBytes(32), false))
bDecrypt(b, stupidgcm.NewAES256GCM(randBytes(32)))
}

func BenchmarkGoGCM(b *testing.B) {
Expand Down
2 changes: 1 addition & 1 deletion internal/stupidgcm/gcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type stupidGCM struct {
// NewAES256GCM returns a new AES-256-GCM cipher that satisfies the cipher.AEAD interface.
//
// Only 32-bytes keys and 16-byte IVs are supported.
func NewAES256GCM(keyIn []byte, forceDecode bool) cipher.AEAD {
func NewAES256GCM(keyIn []byte) cipher.AEAD {
if len(keyIn) != keyLen {
log.Panicf("Only %d-byte keys are supported", keyLen)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/stupidgcm/gcm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func TestStupidGCM(t *testing.T) {
key := randBytes(32)
sGCM := NewAES256GCM(key, false)
sGCM := NewAES256GCM(key)

gAES, err := aes.NewCipher(key)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/stupidgcm/without_openssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func errExit() {
os.Exit(exitcodes.OpenSSL)
}

func NewAES256GCM(_ []byte, _ bool) cipher.AEAD {
func NewAES256GCM(_ []byte) cipher.AEAD {
errExit()
return nil
}
Expand Down
9 changes: 2 additions & 7 deletions mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
LongNames: args.longnames,
ConfigCustom: args._configCustom,
NoPrealloc: args.noprealloc,
ForceDecode: args.forcedecode,
ForceOwner: args._forceOwner,
Exclude: args.exclude,
ExcludeWildcard: args.excludeWildcard,
Expand Down Expand Up @@ -323,8 +322,8 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
}

// Init crypto backend
cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf, args.forcedecode)
cEnc := contentenc.New(cCore, contentenc.DefaultBS, args.forcedecode)
cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf)
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames,
args.raw64, []string(args.badname), frontendArgs.DeterministicNames)
// After the crypto backend is initialized,
Expand Down Expand Up @@ -408,10 +407,6 @@ func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server {
if args.acl {
mOpts.EnableAcl = true
}
if args.forcedecode {
tlog.Info.Printf(tlog.ColorYellow + "THE OPTION \"-forcedecode\" IS ACTIVE. GOCRYPTFS WILL RETURN CORRUPT DATA!" +
tlog.ColorReset)
}
// fusermount from libfuse 3.x removed the "nonempty" option and exits
// with an error if it sees it. Only add it to the options on libfuse 2.x.
if args.nonempty && haveFusermount2() {
Expand Down

0 comments on commit d023cd6

Please sign in to comment.