diff options
| -rw-r--r-- | Documentation/MANPAGE.md | 12 | ||||
| -rw-r--r-- | cli_args.go | 25 | ||||
| -rw-r--r-- | internal/configfile/config_file.go | 4 | ||||
| -rw-r--r-- | internal/contentenc/content.go | 16 | ||||
| -rw-r--r-- | internal/contentenc/content_test.go | 12 | ||||
| -rw-r--r-- | internal/cryptocore/cryptocore.go | 4 | ||||
| -rw-r--r-- | internal/cryptocore/cryptocore_test.go | 8 | ||||
| -rw-r--r-- | internal/fusefrontend/args.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/file.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/fs.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfs.go | 4 | ||||
| -rw-r--r-- | internal/speed/speed.go | 2 | ||||
| -rw-r--r-- | internal/stupidgcm/stupidgcm.go | 18 | ||||
| -rw-r--r-- | internal/stupidgcm/stupidgcm_test.go | 4 | ||||
| -rw-r--r-- | internal/stupidgcm/without_openssl.go | 5 | ||||
| -rw-r--r-- | mount.go | 1 | 
16 files changed, 94 insertions, 31 deletions
| diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index 6fb55ca..656a400 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -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 diff --git a/cli_args.go b/cli_args.go index 9414e67..f0bfb48 100644 --- a/cli_args.go +++ b/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 diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index bf56f8b..5605e1d 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -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  } diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index a2a263c..9998c06 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -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 diff --git a/internal/contentenc/content_test.go b/internal/contentenc/content_test.go index 8ce496d..e4d4a3e 100644 --- a/internal/contentenc/content_test.go +++ b/internal/contentenc/content_test.go @@ -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 { diff --git a/internal/cryptocore/cryptocore.go b/internal/cryptocore/cryptocore.go index 5244104..2c352c2 100644 --- a/internal/cryptocore/cryptocore.go +++ b/internal/cryptocore/cryptocore.go @@ -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 { diff --git a/internal/cryptocore/cryptocore_test.go b/internal/cryptocore/cryptocore_test.go index 25f6572..4c34652 100644 --- a/internal/cryptocore/cryptocore_test.go +++ b/internal/cryptocore/cryptocore_test.go @@ -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)  } diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index ddfb9dc..5781db8 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -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  } diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index 84ce058..4d75d64 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -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 diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 28c43b6..4aa4ffd 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -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 { diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index a94f448..3c52244 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -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{ diff --git a/internal/speed/speed.go b/internal/speed/speed.go index f9bf93c..8732576 100644 --- a/internal/speed/speed.go +++ b/internal/speed/speed.go @@ -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++ { diff --git a/internal/stupidgcm/stupidgcm.go b/internal/stupidgcm/stupidgcm.go index a1a5a14..133ee1a 100644 --- a/internal/stupidgcm/stupidgcm.go +++ b/internal/stupidgcm/stupidgcm.go @@ -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 diff --git a/internal/stupidgcm/stupidgcm_test.go b/internal/stupidgcm/stupidgcm_test.go index 3081085..eb322f2 100644 --- a/internal/stupidgcm/stupidgcm_test.go +++ b/internal/stupidgcm/stupidgcm_test.go @@ -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) diff --git a/internal/stupidgcm/without_openssl.go b/internal/stupidgcm/without_openssl.go index 18c5ddc..52d8fa0 100644 --- a/internal/stupidgcm/without_openssl.go +++ b/internal/stupidgcm/without_openssl.go @@ -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{} @@ -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 { | 
