Add -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. Closes https://github.com/rfjakob/gocryptfs/pull/102 .
This commit is contained in:
parent
9777e4bf7e
commit
f1945c4daa
@ -61,6 +61,18 @@ to mount the gocryptfs filesytem without user interaction.
|
||||
Stay in the foreground instead of forking away. Implies "-nosyslog".
|
||||
For compatability, "-f" is also accepted, but "-fg" is preferred.
|
||||
|
||||
#### -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.
|
||||
This option has no effect 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 crpyto.
|
||||
|
||||
#### -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
|
||||
|
25
cli_args.go
25
cli_args.go
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/prefer_openssl"
|
||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
@ -18,7 +19,7 @@ type argContainer struct {
|
||||
debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
|
||||
plaintextnames, quiet, nosyslog, wpanic,
|
||||
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64,
|
||||
noprealloc, speed, hkdf, serialize_reads bool
|
||||
noprealloc, speed, hkdf, serialize_reads, forcedecode bool
|
||||
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
|
||||
memprofile, ko, passfile, ctlsock, fsname string
|
||||
// Configuration file name override
|
||||
@ -113,6 +114,8 @@ func parseCliOpts() (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.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")
|
||||
@ -154,6 +157,26 @@ func parseCliOpts() (args argContainer) {
|
||||
os.Exit(ErrExitUsage)
|
||||
}
|
||||
}
|
||||
// "-forcedecode" only works with openssl. Check compilation and command line parameters
|
||||
if args.forcedecode == true {
|
||||
if stupidgcm.BuiltWithoutOpenssl == true {
|
||||
tlog.Fatal.Printf("The -forcedecode flag requires openssl support, but gocryptfs was compiled without it!")
|
||||
os.Exit(ErrExitUsage)
|
||||
}
|
||||
if args.aessiv == true {
|
||||
tlog.Fatal.Printf("The -forcedecode and -aessiv flags are incompatible because they use different crypto libs (openssl vs native Go)")
|
||||
os.Exit(ErrExitUsage)
|
||||
}
|
||||
if args.reverse == true {
|
||||
tlog.Fatal.Printf("The reverse mode and the -forcedecode option are not compatible")
|
||||
os.Exit(ErrExitUsage)
|
||||
}
|
||||
v, e := strconv.ParseBool(opensslAuto)
|
||||
if e == nil && v == false {
|
||||
tlog.Warn.Printf("-openssl set to true, as it is required by -forcedecode flag")
|
||||
}
|
||||
args.openssl = true
|
||||
}
|
||||
// '-passfile FILE' is a shortcut for -extpass='/bin/cat -- FILE'
|
||||
if args.passfile != "" {
|
||||
args.extpass = "/bin/cat -- " + args.passfile
|
||||
|
@ -225,7 +225,7 @@ func getKeyEncrypter(scryptHash []byte, useHKDF bool) *contentenc.ContentEnc {
|
||||
if useHKDF {
|
||||
IVLen = contentenc.DefaultIVBits
|
||||
}
|
||||
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, IVLen, useHKDF)
|
||||
ce := contentenc.New(cc, 4096)
|
||||
cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, IVLen, useHKDF, false)
|
||||
ce := contentenc.New(cc, 4096, false)
|
||||
return ce
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
@ -46,10 +47,12 @@ 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
|
||||
}
|
||||
|
||||
// New returns an initialized ContentEnc instance.
|
||||
func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
|
||||
func New(cc *cryptocore.CryptoCore, plainBS uint64, forceDecode bool) *ContentEnc {
|
||||
cipherBS := plainBS + uint64(cc.IVLen) + cryptocore.AuthTagLen
|
||||
|
||||
return &ContentEnc{
|
||||
@ -58,6 +61,7 @@ func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc {
|
||||
cipherBS: cipherBS,
|
||||
allZeroBlock: make([]byte, cipherBS),
|
||||
allZeroNonce: make([]byte, cc.IVLen),
|
||||
forceDecode: forceDecode,
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +86,9 @@ func (be *ContentEnc) DecryptBlocks(ciphertext []byte, firstBlockNo uint64, file
|
||||
var pBlock []byte
|
||||
pBlock, err = be.DecryptBlock(cBlock, firstBlockNo, fileID)
|
||||
if err != nil {
|
||||
break
|
||||
if be.forceDecode == false || (be.forceDecode == true && stupidgcm.AuthError != err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
pBuf.Write(pBlock)
|
||||
firstBlockNo++
|
||||
@ -133,7 +139,11 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
|
||||
if err != nil {
|
||||
tlog.Warn.Printf("DecryptBlock: %s, len=%d", err.Error(), len(ciphertextOrig))
|
||||
tlog.Debug.Println(hex.Dump(ciphertextOrig))
|
||||
return nil, err
|
||||
if be.forceDecode == true {
|
||||
return plaintext, err
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
|
@ -23,8 +23,8 @@ func TestSplitRange(t *testing.T) {
|
||||
testRange{6654, 8945})
|
||||
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendOpenSSL, DefaultIVBits, true)
|
||||
f := New(cc, DefaultBS)
|
||||
cc := cryptocore.New(key, cryptocore.BackendOpenSSL, DefaultIVBits, true, false)
|
||||
f := New(cc, DefaultBS, false)
|
||||
|
||||
for _, r := range ranges {
|
||||
parts := f.ExplodePlainRange(r.offset, r.length)
|
||||
@ -51,8 +51,8 @@ func TestCiphertextRange(t *testing.T) {
|
||||
testRange{6654, 8945})
|
||||
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendOpenSSL, DefaultIVBits, true)
|
||||
f := New(cc, DefaultBS)
|
||||
cc := cryptocore.New(key, cryptocore.BackendOpenSSL, DefaultIVBits, true, false)
|
||||
f := New(cc, DefaultBS, false)
|
||||
|
||||
for _, r := range ranges {
|
||||
|
||||
@ -74,8 +74,8 @@ func TestCiphertextRange(t *testing.T) {
|
||||
|
||||
func TestBlockNo(t *testing.T) {
|
||||
key := make([]byte, cryptocore.KeyLen)
|
||||
cc := cryptocore.New(key, cryptocore.BackendOpenSSL, DefaultIVBits, true)
|
||||
f := New(cc, DefaultBS)
|
||||
cc := cryptocore.New(key, cryptocore.BackendOpenSSL, DefaultIVBits, true, false)
|
||||
f := New(cc, DefaultBS, false)
|
||||
|
||||
b := f.CipherOffToBlockNo(788)
|
||||
if b != 0 {
|
||||
|
@ -51,7 +51,7 @@ type CryptoCore struct {
|
||||
// Even though the "GCMIV128" feature flag is now mandatory, we must still
|
||||
// support 96-bit IVs here because they were used for encrypting the master
|
||||
// key in gocryptfs.conf up to gocryptfs v1.2. v1.3 switched to 128 bits.
|
||||
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool) *CryptoCore {
|
||||
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDecode bool) *CryptoCore {
|
||||
if len(key) != KeyLen {
|
||||
log.Panic(fmt.Sprintf("Unsupported key length %d", len(key)))
|
||||
}
|
||||
@ -86,7 +86,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool) *CryptoC
|
||||
if IVLen != 16 {
|
||||
log.Panic("stupidgcm only supports 128-bit IVs")
|
||||
}
|
||||
aeadCipher = stupidgcm.New(gcmKey)
|
||||
aeadCipher = stupidgcm.New(gcmKey, forceDecode)
|
||||
case BackendGoGCM:
|
||||
goGcmBlockCipher, err := aes.NewCipher(gcmKey)
|
||||
if err != nil {
|
||||
|
@ -8,15 +8,15 @@ import (
|
||||
func TestCryptoCoreNew(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
for _, useHKDF := range []bool{true, false} {
|
||||
c := New(key, BackendOpenSSL, 128, useHKDF)
|
||||
c := New(key, BackendOpenSSL, 128, useHKDF, false)
|
||||
if c.IVLen != 16 {
|
||||
t.Fail()
|
||||
}
|
||||
c = New(key, BackendGoGCM, 96, useHKDF)
|
||||
c = New(key, BackendGoGCM, 96, useHKDF, false)
|
||||
if c.IVLen != 12 {
|
||||
t.Fail()
|
||||
}
|
||||
c = New(key, BackendGoGCM, 128, useHKDF)
|
||||
c = New(key, BackendGoGCM, 128, useHKDF, false)
|
||||
if c.IVLen != 16 {
|
||||
t.Fail()
|
||||
}
|
||||
@ -32,5 +32,5 @@ func TestNewPanic(t *testing.T) {
|
||||
}()
|
||||
|
||||
key := make([]byte, 16)
|
||||
New(key, BackendOpenSSL, 128, true)
|
||||
New(key, BackendOpenSSL, 128, true, false)
|
||||
}
|
||||
|
@ -31,4 +31,6 @@ type Args struct {
|
||||
HKDF bool
|
||||
// Try to serialize read operations, "-serialize_reads"
|
||||
SerializeReads bool
|
||||
// Force decode even if integrity check fails (openSSL only)
|
||||
ForceDecode bool
|
||||
}
|
||||
|
@ -202,7 +202,9 @@ func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
|
||||
if err != nil {
|
||||
curruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
|
||||
tlog.Warn.Printf("ino%d: doRead: corrupt block #%d: %v", f.devIno.ino, curruptBlockNo, err)
|
||||
return nil, fuse.EIO
|
||||
if (f.fs.args.ForceDecode == false) {
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
}
|
||||
|
||||
// Crop down to the relevant part
|
||||
|
@ -40,8 +40,8 @@ var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented.
|
||||
|
||||
// NewFS returns a new encrypted FUSE overlay filesystem.
|
||||
func NewFS(args Args) *FS {
|
||||
cryptoCore := cryptocore.New(args.Masterkey, args.CryptoBackend, contentenc.DefaultIVBits, args.HKDF)
|
||||
contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS)
|
||||
cryptoCore := cryptocore.New(args.Masterkey, args.CryptoBackend, contentenc.DefaultIVBits, args.HKDF, args.ForceDecode)
|
||||
contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS, args.ForceDecode)
|
||||
nameTransform := nametransform.New(cryptoCore.EMECipher, args.LongNames, args.Raw64)
|
||||
|
||||
if args.SerializeReads {
|
||||
|
@ -44,8 +44,8 @@ func NewFS(args fusefrontend.Args) *ReverseFS {
|
||||
log.Panic("reverse mode must use AES-SIV, everything else is insecure")
|
||||
}
|
||||
initLongnameCache()
|
||||
cryptoCore := cryptocore.New(args.Masterkey, args.CryptoBackend, contentenc.DefaultIVBits, args.HKDF)
|
||||
contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS)
|
||||
cryptoCore := cryptocore.New(args.Masterkey, args.CryptoBackend, contentenc.DefaultIVBits, args.HKDF, false)
|
||||
contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS, false)
|
||||
nameTransform := nametransform.New(cryptoCore.EMECipher, args.LongNames, args.Raw64)
|
||||
|
||||
return &ReverseFS{
|
||||
|
@ -72,7 +72,7 @@ func bStupidGCM(b *testing.B) {
|
||||
in := make([]byte, blockSize)
|
||||
b.SetBytes(int64(len(in)))
|
||||
|
||||
sGCM := stupidgcm.New(key)
|
||||
sGCM := stupidgcm.New(key, false)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -26,17 +26,21 @@ const (
|
||||
|
||||
// stupidGCM implements the cipher.AEAD interface
|
||||
type stupidGCM struct {
|
||||
key []byte
|
||||
key []byte
|
||||
forceDecode bool
|
||||
}
|
||||
|
||||
//authentication error
|
||||
var AuthError error = fmt.Errorf("stupidgcm: message authentication failed")
|
||||
|
||||
var _ cipher.AEAD = &stupidGCM{}
|
||||
|
||||
// New returns a new cipher.AEAD implementation..
|
||||
func New(key []byte) cipher.AEAD {
|
||||
func New(key []byte, forceDecode bool) cipher.AEAD {
|
||||
if len(key) != keyLen {
|
||||
log.Panicf("Only %d-byte keys are supported", keyLen)
|
||||
}
|
||||
return stupidGCM{key: key}
|
||||
return stupidGCM{key: key, forceDecode: forceDecode}
|
||||
}
|
||||
|
||||
func (g stupidGCM) NonceSize() int {
|
||||
@ -186,7 +190,13 @@ func (g stupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) {
|
||||
C.EVP_CIPHER_CTX_free(ctx)
|
||||
|
||||
if res != 1 {
|
||||
return nil, fmt.Errorf("stupidgcm: message authentication failed")
|
||||
// The error code must always be checked by the calling function, because the decrypted buffer
|
||||
// may contain corrupted data that we are returning in case the user forced reads
|
||||
if g.forceDecode == true {
|
||||
return append(dst, buf...), AuthError
|
||||
} else {
|
||||
return nil, AuthError
|
||||
}
|
||||
}
|
||||
|
||||
return append(dst, buf...), nil
|
||||
|
@ -27,7 +27,7 @@ func randBytes(n int) []byte {
|
||||
// GCM implemenatation and verifies that the results are identical.
|
||||
func TestEncryptDecrypt(t *testing.T) {
|
||||
key := randBytes(32)
|
||||
sGCM := New(key)
|
||||
sGCM := New(key, false)
|
||||
authData := randBytes(24)
|
||||
iv := randBytes(16)
|
||||
dst := make([]byte, 71) // 71 = random length
|
||||
@ -77,7 +77,7 @@ func TestEncryptDecrypt(t *testing.T) {
|
||||
// error
|
||||
func TestCorruption(t *testing.T) {
|
||||
key := randBytes(32)
|
||||
sGCM := New(key)
|
||||
sGCM := New(key, false)
|
||||
authData := randBytes(24)
|
||||
iv := randBytes(16)
|
||||
|
||||
|
@ -14,12 +14,15 @@ const (
|
||||
BuiltWithoutOpenssl = true
|
||||
)
|
||||
|
||||
//authentication error - needed to compile as same varaible is exported when openssl is enable via stupidgcm.go
|
||||
var AuthError error = fmt.Errorf("stupidgcm: message authentication failed with openssl disabled!")
|
||||
|
||||
func errExit() {
|
||||
fmt.Fprintln(os.Stderr, "gocryptfs has been compiled without openssl support but you are still trying to use openssl")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func New(_ []byte) stupidGCM {
|
||||
func New(_ []byte, _ bool) stupidGCM {
|
||||
errExit()
|
||||
// Never reached
|
||||
return stupidGCM{}
|
||||
|
1
mount.go
1
mount.go
@ -192,6 +192,7 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF
|
||||
NoPrealloc: args.noprealloc,
|
||||
HKDF: args.hkdf,
|
||||
SerializeReads: args.serialize_reads,
|
||||
ForceDecode: args.forcedecode,
|
||||
}
|
||||
// confFile is nil when "-zerokey" or "-masterkey" was used
|
||||
if confFile != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user