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.
This commit is contained in:
parent
4764a9bde0
commit
97d8340bd8
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
67
internal/configfile/validate.go
Normal file
67
internal/configfile/validate.go
Normal file
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user