aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2021-08-21 21:43:26 +0200
committerJakob Unterwurzacher2021-08-23 16:00:41 +0200
commit97d8340bd81ddd60baac598d3e25ebfb4decb50c (patch)
tree2f5444d523ca142e847b0b51422bc51ad8203a75
parent4764a9bde093f6b61d0370653c6c9d12949ed145 (diff)
configfile: add Validate() function, support FlagXChaCha20Poly1305
We used to do validation using lists of mandatory feature flags. With the introduction of XChaCha20Poly1305, this became too simplistic, as it uses a different IV length, hence disabling GCMIV128. Add a dedicated function, Validate(), with open-coded validation logic. The validation and creation logic also gets XChaCha20Poly1305 support, and gocryptfs -init -xchacha now writes the flag into gocryptfs.conf.
-rw-r--r--init_dir.go3
-rw-r--r--internal/configfile/config_file.go93
-rw-r--r--internal/configfile/config_test.go5
-rw-r--r--internal/configfile/feature_flags.go17
-rw-r--r--internal/configfile/scrypt.go25
-rw-r--r--internal/configfile/validate.go67
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
+}