summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 {