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:
Jakob Unterwurzacher 2021-08-21 21:43:26 +02:00
parent 4764a9bde0
commit 97d8340bd8
6 changed files with 126 additions and 84 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}

View 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
}