aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanim72017-04-08 02:09:28 +0200
committerJakob Unterwurzacher2017-04-23 23:11:56 +0200
commitf1945c4daae65074cfca8f0ab5b97ac5a50c24a0 (patch)
treef6a555c9d7fedb0da6f5e21981f4154fa413c8c0
parent9777e4bf7ea2aa75ab443dc6e15c42103eb6b027 (diff)
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 .
-rw-r--r--Documentation/MANPAGE.md12
-rw-r--r--cli_args.go25
-rw-r--r--internal/configfile/config_file.go4
-rw-r--r--internal/contentenc/content.go16
-rw-r--r--internal/contentenc/content_test.go12
-rw-r--r--internal/cryptocore/cryptocore.go4
-rw-r--r--internal/cryptocore/cryptocore_test.go8
-rw-r--r--internal/fusefrontend/args.go2
-rw-r--r--internal/fusefrontend/file.go4
-rw-r--r--internal/fusefrontend/fs.go4
-rw-r--r--internal/fusefrontend_reverse/rfs.go4
-rw-r--r--internal/speed/speed.go2
-rw-r--r--internal/stupidgcm/stupidgcm.go18
-rw-r--r--internal/stupidgcm/stupidgcm_test.go4
-rw-r--r--internal/stupidgcm/without_openssl.go5
-rw-r--r--mount.go1
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{}
diff --git a/mount.go b/mount.go
index c4bdc4d..6da6492 100644
--- a/mount.go
+++ b/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 {