diff options
| -rw-r--r-- | init_dir.go | 3 | ||||
| -rw-r--r-- | internal/configfile/config_file.go | 93 | ||||
| -rw-r--r-- | internal/configfile/config_test.go | 5 | ||||
| -rw-r--r-- | internal/configfile/feature_flags.go | 17 | ||||
| -rw-r--r-- | internal/configfile/scrypt.go | 25 | ||||
| -rw-r--r-- | internal/configfile/validate.go | 67 | 
6 files changed, 126 insertions, 84 deletions
| diff --git a/init_dir.go b/init_dir.go index 8f11351..c6539c8 100644 --- a/init_dir.go +++ b/init_dir.go @@ -96,7 +96,8 @@ func initDir(args *argContainer) {  			Devrandom:          args.devrandom,  			Fido2CredentialID:  fido2CredentialID,  			Fido2HmacSalt:      fido2HmacSalt, -			DeterministicNames: args.deterministic_names}) +			DeterministicNames: args.deterministic_names, +			XChaCha20Poly1305:  args.xchacha})  		if err != nil {  			tlog.Fatal.Println(err)  			os.Exit(exitcodes.WriteConf) diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go index d457db6..dba6c47 100644 --- a/internal/configfile/config_file.go +++ b/internal/configfile/config_file.go @@ -88,40 +88,52 @@ type CreateArgs struct {  	Fido2CredentialID  []byte  	Fido2HmacSalt      []byte  	DeterministicNames bool +	XChaCha20Poly1305  bool  }  // Create - create a new config with a random key encrypted with  // "Password" and write it to "Filename".  // Uses scrypt with cost parameter "LogN".  func Create(args *CreateArgs) error { -	var cf ConfFile -	cf.filename = args.Filename -	cf.Creator = args.Creator -	cf.Version = contentenc.CurrentVersion - -	// Set feature flags -	cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMIV128]) -	cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagHKDF]) +	cf := ConfFile{ +		filename: args.Filename, +		Creator:  args.Creator, +		Version:  contentenc.CurrentVersion, +	} +	// Feature flags +	cf.setFeatureFlag(FlagHKDF) +	if args.XChaCha20Poly1305 { +		cf.setFeatureFlag(FlagXChaCha20Poly1305) +	} else { +		// 128-bit IVs are mandatory for AES-GCM (default is 96!) and AES-SIV, +		// XChaCha20Poly1305 uses even an even longer IV of 192 bits. +		cf.setFeatureFlag(FlagGCMIV128) +	}  	if args.PlaintextNames { -		cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagPlaintextNames]) +		cf.setFeatureFlag(FlagPlaintextNames)  	} else {  		if !args.DeterministicNames { -			cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagDirIV]) +			cf.setFeatureFlag(FlagDirIV)  		} -		cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames]) -		cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames]) -		cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagRaw64]) +		cf.setFeatureFlag(FlagEMENames) +		cf.setFeatureFlag(FlagLongNames) +		cf.setFeatureFlag(FlagRaw64)  	}  	if args.AESSIV { -		cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV]) +		cf.setFeatureFlag(FlagAESSIV)  	}  	if len(args.Fido2CredentialID) > 0 { -		cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2]) +		cf.setFeatureFlag(FlagFIDO2)  		cf.FIDO2 = &FIDO2Params{  			CredentialID: args.Fido2CredentialID,  			HMACSalt:     args.Fido2HmacSalt,  		}  	} +	// Catch bugs and invalid cli flag combinations early +	cf.ScryptObject = NewScryptKDF(args.LogN) +	if err := cf.Validate(); err != nil { +		return err +	}  	{  		// Generate new random master key  		var key []byte @@ -193,50 +205,22 @@ func Load(filename string) (*ConfFile, error) {  		return nil, err  	} -	if cf.Version != contentenc.CurrentVersion { -		return nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version) -	} - -	// Check that all set feature flags are known -	for _, flag := range cf.FeatureFlags { -		if !cf.isFeatureFlagKnown(flag) { -			return nil, fmt.Errorf("Unsupported feature flag %q", flag) -		} -	} - -	// Check that all required feature flags are set -	var requiredFlags []flagIota -	if cf.IsFeatureFlagSet(FlagPlaintextNames) { -		requiredFlags = requiredFlagsPlaintextNames -	} else { -		requiredFlags = requiredFlagsNormal -	} -	deprecatedFs := false -	for _, i := range requiredFlags { -		if !cf.IsFeatureFlagSet(i) { -			fmt.Fprintf(os.Stderr, "Required feature flag %q is missing\n", knownFlags[i]) -			deprecatedFs = true -		} -	} -	if deprecatedFs { -		fmt.Fprintf(os.Stderr, tlog.ColorYellow+` -    The filesystem was created by gocryptfs v0.6 or earlier. This version of -    gocryptfs can no longer mount the filesystem. -    Please download gocryptfs v0.11 and upgrade your filesystem, -    see https://github.com/rfjakob/gocryptfs/v2/wiki/Upgrading for instructions. - -    If you have trouble upgrading, join the discussion at -    https://github.com/rfjakob/gocryptfs/v2/issues/29 . - -`+tlog.ColorReset) - -		return nil, exitcodes.NewErr("Deprecated filesystem", exitcodes.DeprecatedFS) +	if err := cf.Validate(); err != nil { +		return nil, exitcodes.NewErr(err.Error(), exitcodes.DeprecatedFS)  	}  	// All good  	return &cf, nil  } +func (cf *ConfFile) setFeatureFlag(flag flagIota) { +	if cf.IsFeatureFlagSet(flag) { +		// Already set, ignore +		return +	} +	cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[flag]) +} +  // DecryptMasterKey decrypts the masterkey stored in cf.EncryptedKey using  // password.  func (cf *ConfFile) DecryptMasterKey(password []byte) (masterkey []byte, err error) { @@ -293,6 +277,9 @@ func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int) {  // then rename over "filename".  // This way a password change atomically replaces the file.  func (cf *ConfFile) WriteFile() error { +	if err := cf.Validate(); err != nil { +		return err +	}  	tmp := cf.filename + ".tmp"  	// 0400 permissions: gocryptfs.conf should be kept secret and never be written to.  	fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0400) diff --git a/internal/configfile/config_test.go b/internal/configfile/config_test.go index 6986888..021b6c1 100644 --- a/internal/configfile/config_test.go +++ b/internal/configfile/config_test.go @@ -152,15 +152,14 @@ func TestIsFeatureFlagKnown(t *testing.T) {  		testKnownFlags = append(testKnownFlags, f)  	} -	var cf ConfFile  	for _, f := range testKnownFlags { -		if !cf.isFeatureFlagKnown(f) { +		if !isFeatureFlagKnown(f) {  			t.Errorf("flag %q should be known", f)  		}  	}  	f := "StrangeFeatureFlag" -	if cf.isFeatureFlagKnown(f) { +	if isFeatureFlagKnown(f) {  		t.Errorf("flag %q should be NOT known", f)  	}  } diff --git a/internal/configfile/feature_flags.go b/internal/configfile/feature_flags.go index be5616f..e28abd6 100644 --- a/internal/configfile/feature_flags.go +++ b/internal/configfile/feature_flags.go @@ -11,7 +11,8 @@ const (  	// This flag is mandatory since gocryptfs v1.0.  	FlagEMENames  	// FlagGCMIV128 indicates 128-bit GCM IVs. -	// This flag is mandatory since gocryptfs v1.0. +	// This flag is mandatory since gocryptfs v1.0, +	// except when XChaCha20Poly1305 is used.  	FlagGCMIV128  	// FlagLongNames allows file names longer than 176 bytes.  	FlagLongNames @@ -46,20 +47,8 @@ var knownFlags = map[flagIota]string{  	FlagXChaCha20Poly1305: "XChaCha20Poly1305",  } -// Filesystems that do not have these feature flags set are deprecated. -var requiredFlagsNormal = []flagIota{ -	FlagEMENames, -	FlagGCMIV128, -} - -// Filesystems without filename encryption obviously don't have or need the -// filename related feature flags. -var requiredFlagsPlaintextNames = []flagIota{ -	FlagGCMIV128, -} -  // isFeatureFlagKnown verifies that we understand a feature flag. -func (cf *ConfFile) isFeatureFlagKnown(flag string) bool { +func isFeatureFlagKnown(flag string) bool {  	for _, knownFlag := range knownFlags {  		if knownFlag == flag {  			return true diff --git a/internal/configfile/scrypt.go b/internal/configfile/scrypt.go index 7ac822e..0ce8777 100644 --- a/internal/configfile/scrypt.go +++ b/internal/configfile/scrypt.go @@ -1,6 +1,7 @@  package configfile  import ( +	"fmt"  	"log"  	"math"  	"os" @@ -62,8 +63,10 @@ func NewScryptKDF(logN int) ScryptKDF {  // DeriveKey returns a new key from a supplied password.  func (s *ScryptKDF) DeriveKey(pw []byte) []byte { -	s.validateParams() - +	if err := s.validateParams(); err != nil { +		tlog.Fatal.Println(err.Error()) +		os.Exit(exitcodes.ScryptParams) +	}  	k, err := scrypt.Key(pw, s.Salt, s.N, s.R, s.P, s.KeyLen)  	if err != nil {  		log.Panicf("DeriveKey failed: %v", err) @@ -81,26 +84,22 @@ func (s *ScryptKDF) LogN() int {  // If not, it exists with an error message.  // This makes sure we do not get weak parameters passed through a  // rougue gocryptfs.conf. -func (s *ScryptKDF) validateParams() { +func (s *ScryptKDF) validateParams() error {  	minN := 1 << scryptMinLogN  	if s.N < minN { -		tlog.Fatal.Println("Fatal: scryptn below 10 is too low to make sense") -		os.Exit(exitcodes.ScryptParams) +		return fmt.Errorf("Fatal: scryptn below 10 is too low to make sense")  	}  	if s.R < scryptMinR { -		tlog.Fatal.Printf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR) -		os.Exit(exitcodes.ScryptParams) +		return fmt.Errorf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)  	}  	if s.P < scryptMinP { -		tlog.Fatal.Printf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP) -		os.Exit(exitcodes.ScryptParams) +		return fmt.Errorf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)  	}  	if len(s.Salt) < scryptMinSaltLen { -		tlog.Fatal.Printf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen) -		os.Exit(exitcodes.ScryptParams) +		return fmt.Errorf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)  	}  	if s.KeyLen < cryptocore.KeyLen { -		tlog.Fatal.Printf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen) -		os.Exit(exitcodes.ScryptParams) +		return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)  	} +	return nil  } diff --git a/internal/configfile/validate.go b/internal/configfile/validate.go new file mode 100644 index 0000000..511f704 --- /dev/null +++ b/internal/configfile/validate.go @@ -0,0 +1,67 @@ +package configfile + +import ( +	"fmt" + +	"github.com/rfjakob/gocryptfs/v2/internal/contentenc" +) + +// Validate that the combination of settings makes sense and is supported +func (cf *ConfFile) Validate() error { +	if cf.Version != contentenc.CurrentVersion { +		return fmt.Errorf("Unsupported on-disk format %d", cf.Version) +	} +	// scrypt params ok? +	if err := cf.ScryptObject.validateParams(); err != nil { +		return err +	} +	// All feature flags that are in the config file are known? +	for _, flag := range cf.FeatureFlags { +		if !isFeatureFlagKnown(flag) { +			return fmt.Errorf("Unknown feature flag %q", flag) +		} +	} +	// File content encryption +	{ +		switch { +		case cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && cf.IsFeatureFlagSet(FlagAESSIV): +			return fmt.Errorf("Can't have both XChaCha20Poly1305 and AESSIV feature flags") +		case cf.IsFeatureFlagSet(FlagAESSIV): +			if !cf.IsFeatureFlagSet(FlagGCMIV128) { +				return fmt.Errorf("AESSIV requires GCMIV128 feature flag") +			} +		case cf.IsFeatureFlagSet(FlagXChaCha20Poly1305): +			if cf.IsFeatureFlagSet(FlagGCMIV128) { +				return fmt.Errorf("XChaCha20Poly1305 conflicts with GCMIV128 feature flag") +			} +			if !cf.IsFeatureFlagSet(FlagHKDF) { +				return fmt.Errorf("XChaCha20Poly1305 requires HKDF feature flag") +			} +		// The absence of other flags means AES-GCM (oldest algorithm) +		case !cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && !cf.IsFeatureFlagSet(FlagAESSIV): +			if !cf.IsFeatureFlagSet(FlagGCMIV128) { +				return fmt.Errorf("AES-GCM requires GCMIV128 feature flag") +			} +		} +	} +	// Filename encryption +	{ +		switch { +		case cf.IsFeatureFlagSet(FlagPlaintextNames) && cf.IsFeatureFlagSet(FlagEMENames): +			return fmt.Errorf("Can't have both PlaintextNames and EMENames feature flags") +		case cf.IsFeatureFlagSet(FlagPlaintextNames): +			if cf.IsFeatureFlagSet(FlagDirIV) { +				return fmt.Errorf("PlaintextNames conflicts with DirIV feature flag") +			} +			if cf.IsFeatureFlagSet(FlagLongNames) { +				return fmt.Errorf("PlaintextNames conflicts with LongNames feature flag") +			} +			if cf.IsFeatureFlagSet(FlagRaw64) { +				return fmt.Errorf("PlaintextNames conflicts with Raw64 feature flag") +			} +		case cf.IsFeatureFlagSet(FlagEMENames): +			// All combinations of DirIV, LongNames, Raw64 allowed +		} +	} +	return nil +} | 
