diff --git a/init_dir.go b/init_dir.go index c52e2fa..fac4053 100644 --- a/init_dir.go +++ b/init_dir.go @@ -39,7 +39,7 @@ func initDir(args *argContainer) { } password := readpassword.Twice(args.extpass) creator := tlog.ProgramName + " " + GitVersion - err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator) + err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.reverse) if err != nil { tlog.Fatal.Println(err) os.Exit(ERREXIT_INIT) diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index 178890b..b1504b4 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -45,7 +45,7 @@ type ConfFile struct { // CreateConfFile - create a new config with a random key encrypted with // "password" and write it to "filename". // Uses scrypt with cost parameter logN. -func CreateConfFile(filename string, password string, plaintextNames bool, logN int, creator string) error { +func CreateConfFile(filename string, password string, plaintextNames bool, logN int, creator string, reverse bool) error { var cf ConfFile cf.filename = filename cf.Creator = creator @@ -67,6 +67,9 @@ func CreateConfFile(filename string, password string, plaintextNames bool, logN cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames]) cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames]) } + if reverse { + cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMSIV]) + } // Write file to disk return cf.WriteFile() @@ -165,7 +168,7 @@ func (cf *ConfFile) EncryptKey(key []byte, password string, logN int) { // Lock master key using password-based key cc := cryptocore.New(scryptHash, cryptocore.BackendGoGCM, 96) ce := contentenc.New(cc, 4096) - cf.EncryptedKey = ce.EncryptBlock(key, 0, nil) + cf.EncryptedKey = ce.EncryptBlock(key, 0, nil, contentenc.RandomNonce) } // WriteFile - write out config in JSON format to file "filename.tmp" diff --git a/internal/configfile/config_test.go b/internal/configfile/config_test.go index e34a3cd..72c25f6 100644 --- a/internal/configfile/config_test.go +++ b/internal/configfile/config_test.go @@ -60,7 +60,7 @@ func TestLoadV2StrangeFeature(t *testing.T) { } func TestCreateConfFile(t *testing.T) { - err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test") + err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", false) if err != nil { t.Fatal(err) } @@ -71,6 +71,20 @@ func TestCreateConfFile(t *testing.T) { } +func TestCreateConfFileReverse(t *testing.T) { + err := CreateConfFile("config_test/tmp.conf", "test", false, 10, "test", true) + if err != nil { + t.Fatal(err) + } + _, c, err := LoadConfFile("config_test/tmp.conf", "test") + if err != nil { + t.Fatal(err) + } + if !c.IsFeatureFlagSet(FlagGCMSIV) { + t.Error("GCMSIV flag should be set but is not") + } +} + func TestIsFeatureFlagKnown(t *testing.T) { // Test a few hardcoded values testKnownFlags := []string{"DirIV", "PlaintextNames", "EMENames", "GCMIV128", "LongNames"} diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go index e132536..c638221 100644 --- a/internal/contentenc/content.go +++ b/internal/contentenc/content.go @@ -11,11 +11,17 @@ import ( "github.com/rfjakob/gocryptfs/internal/tlog" ) +type NonceMode int + const ( // Default plaintext block size DefaultBS = 4096 // We always use 128-bit IVs for file content encryption IVBitLen = 128 + + _ = iota // skip zero + RandomNonce NonceMode = iota + ReverseDummyNonce NonceMode = iota ) type ContentEnc struct { @@ -27,6 +33,8 @@ type ContentEnc struct { cipherBS uint64 // All-zero block of size cipherBS, for fast compares allZeroBlock []byte + // All-zero block of size IVBitLen/8, for fast compares + allZeroNonce []byte } func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc { @@ -38,6 +46,7 @@ func New(cc *cryptocore.CryptoCore, plainBS uint64) *ContentEnc { plainBS: plainBS, cipherBS: cipherBS, allZeroBlock: make([]byte, cipherBS), + allZeroNonce: make([]byte, IVBitLen/8), } } @@ -94,6 +103,9 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []b // Extract nonce nonce := ciphertext[:be.cryptoCore.IVLen] + if bytes.Equal(nonce, be.allZeroNonce) && be.cryptoCore.AEADBackend != cryptocore.BackendGCMSIV { + panic("Hit an all-zero nonce with GCMSIV off. This MUST NOT happen!") + } ciphertextOrig := ciphertext ciphertext = ciphertext[be.cryptoCore.IVLen:] @@ -115,27 +127,38 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileId []b // EncryptBlocks - Encrypt a number of blocks // Used for reverse mode -func (be *ContentEnc) EncryptBlocks(plaintext []byte, firstBlockNo uint64, fileId []byte) []byte { +func (be *ContentEnc) EncryptBlocks(plaintext []byte, firstBlockNo uint64, fileId []byte, nMode NonceMode) []byte { inBuf := bytes.NewBuffer(plaintext) var outBuf bytes.Buffer for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ { inBlock := inBuf.Next(int(be.plainBS)) - outBlock := be.EncryptBlock(inBlock, blockNo, fileId) + outBlock := be.EncryptBlock(inBlock, blockNo, fileId, nMode) outBuf.Write(outBlock) } return outBuf.Bytes() } // encryptBlock - Encrypt and add IV and MAC -func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte) []byte { - +func (be *ContentEnc) EncryptBlock(plaintext []byte, blockNo uint64, fileID []byte, nMode NonceMode) []byte { // Empty block? if len(plaintext) == 0 { return plaintext } - // Get fresh nonce - nonce := be.cryptoCore.IVGenerator.Get() + var nonce []byte + switch nMode { + case ReverseDummyNonce: + if be.cryptoCore.AEADBackend != cryptocore.BackendGCMSIV { + panic("MUST NOT use dummy nonces unless in GCMSIV mode!") + } + nonce = make([]byte, IVBitLen/8) + binary.BigEndian.PutUint64(nonce, blockNo) + case RandomNonce: + // Get a fresh random nonce + nonce = be.cryptoCore.IVGenerator.Get() + default: + panic("invalid nonce mode") + } // Authenticate block with block number and file ID aData := make([]byte, 8) diff --git a/internal/cryptocore/cryptocore.go b/internal/cryptocore/cryptocore.go index a6708bd..0913ed0 100644 --- a/internal/cryptocore/cryptocore.go +++ b/internal/cryptocore/cryptocore.go @@ -29,6 +29,8 @@ type CryptoCore struct { BlockCipher cipher.Block // GCM or GCM-SIV. This is used for content encryption. AEADCipher cipher.AEAD + // Which backend is behind AEADCipher? + AEADBackend BackendTypeEnum // GCM needs unique IVs (nonces) IVGenerator *nonceGenerator IVLen int @@ -74,6 +76,7 @@ func New(key []byte, backend BackendTypeEnum, IVBitLen int) *CryptoCore { return &CryptoCore{ BlockCipher: blockCipher, AEADCipher: gcm, + AEADBackend: backend, IVGenerator: &nonceGenerator{nonceLen: IVLen}, IVLen: IVLen, } diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index a04b6af..b9edc76 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -256,7 +256,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) { // Encrypt blockOffset := b.BlockCipherOff() - blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id) + blockData = f.contentEnc.EncryptBlock(blockData, b.BlockNo, f.header.Id, contentenc.RandomNonce) tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d", f.ino, uint64(len(blockData))-f.contentEnc.BlockOverhead(), b.BlockNo) diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 575865e..a3db3dc 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -326,7 +326,7 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co return fuse.ToStatus(err) } // Symlinks are encrypted like file contents (GCM) and base64-encoded - cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil) + cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil, contentenc.RandomNonce) cTarget := base64.URLEncoding.EncodeToString(cBinTarget) // Handle long file name diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go index 75ebfd9..7e54b17 100644 --- a/internal/fusefrontend_reverse/rfile.go +++ b/internal/fusefrontend_reverse/rfile.go @@ -64,7 +64,7 @@ func (rf *reverseFile) readBackingFile(off uint64, length uint64) (out []byte, e plaintext = plaintext[0:n] // Encrypt blocks - ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, zeroFileHeader.Id) + ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, zeroFileHeader.Id, contentenc.ReverseDummyNonce) // Crop down to the relevant part lenHave := len(ciphertext) diff --git a/main.go b/main.go index 1877779..68f6a44 100644 --- a/main.go +++ b/main.go @@ -305,6 +305,9 @@ func initFuseFrontend(key []byte, args argContainer, confFile *configfile.ConfFi frontendArgs.PlaintextNames = confFile.IsFeatureFlagSet(configfile.FlagPlaintextNames) if confFile.IsFeatureFlagSet(configfile.FlagGCMSIV) { frontendArgs.CryptoBackend = cryptocore.BackendGCMSIV + } else if args.reverse { + tlog.Fatal.Printf("GCMSIV is required by reverse mode, but not enabled in the config file") + os.Exit(ERREXIT_USAGE) } } // If allow_other is set and we run as root, try to give newly created files to