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,
|
Devrandom: args.devrandom,
|
||||||
Fido2CredentialID: fido2CredentialID,
|
Fido2CredentialID: fido2CredentialID,
|
||||||
Fido2HmacSalt: fido2HmacSalt,
|
Fido2HmacSalt: fido2HmacSalt,
|
||||||
DeterministicNames: args.deterministic_names})
|
DeterministicNames: args.deterministic_names,
|
||||||
|
XChaCha20Poly1305: args.xchacha})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Println(err)
|
tlog.Fatal.Println(err)
|
||||||
os.Exit(exitcodes.WriteConf)
|
os.Exit(exitcodes.WriteConf)
|
||||||
|
@ -88,40 +88,52 @@ type CreateArgs struct {
|
|||||||
Fido2CredentialID []byte
|
Fido2CredentialID []byte
|
||||||
Fido2HmacSalt []byte
|
Fido2HmacSalt []byte
|
||||||
DeterministicNames bool
|
DeterministicNames bool
|
||||||
|
XChaCha20Poly1305 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create - create a new config with a random key encrypted with
|
// Create - create a new config with a random key encrypted with
|
||||||
// "Password" and write it to "Filename".
|
// "Password" and write it to "Filename".
|
||||||
// Uses scrypt with cost parameter "LogN".
|
// Uses scrypt with cost parameter "LogN".
|
||||||
func Create(args *CreateArgs) error {
|
func Create(args *CreateArgs) error {
|
||||||
var cf ConfFile
|
cf := ConfFile{
|
||||||
cf.filename = args.Filename
|
filename: args.Filename,
|
||||||
cf.Creator = args.Creator
|
Creator: args.Creator,
|
||||||
cf.Version = contentenc.CurrentVersion
|
Version: contentenc.CurrentVersion,
|
||||||
|
}
|
||||||
// Set feature flags
|
// Feature flags
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMIV128])
|
cf.setFeatureFlag(FlagHKDF)
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[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 {
|
if args.PlaintextNames {
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagPlaintextNames])
|
cf.setFeatureFlag(FlagPlaintextNames)
|
||||||
} else {
|
} else {
|
||||||
if !args.DeterministicNames {
|
if !args.DeterministicNames {
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagDirIV])
|
cf.setFeatureFlag(FlagDirIV)
|
||||||
}
|
}
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames])
|
cf.setFeatureFlag(FlagEMENames)
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames])
|
cf.setFeatureFlag(FlagLongNames)
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagRaw64])
|
cf.setFeatureFlag(FlagRaw64)
|
||||||
}
|
}
|
||||||
if args.AESSIV {
|
if args.AESSIV {
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV])
|
cf.setFeatureFlag(FlagAESSIV)
|
||||||
}
|
}
|
||||||
if len(args.Fido2CredentialID) > 0 {
|
if len(args.Fido2CredentialID) > 0 {
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2])
|
cf.setFeatureFlag(FlagFIDO2)
|
||||||
cf.FIDO2 = &FIDO2Params{
|
cf.FIDO2 = &FIDO2Params{
|
||||||
CredentialID: args.Fido2CredentialID,
|
CredentialID: args.Fido2CredentialID,
|
||||||
HMACSalt: args.Fido2HmacSalt,
|
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
|
// Generate new random master key
|
||||||
var key []byte
|
var key []byte
|
||||||
@ -193,50 +205,22 @@ func Load(filename string) (*ConfFile, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cf.Version != contentenc.CurrentVersion {
|
if err := cf.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version)
|
return nil, exitcodes.NewErr(err.Error(), exitcodes.DeprecatedFS)
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All good
|
// All good
|
||||||
return &cf, nil
|
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
|
// DecryptMasterKey decrypts the masterkey stored in cf.EncryptedKey using
|
||||||
// password.
|
// password.
|
||||||
func (cf *ConfFile) DecryptMasterKey(password []byte) (masterkey []byte, err error) {
|
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".
|
// then rename over "filename".
|
||||||
// This way a password change atomically replaces the file.
|
// This way a password change atomically replaces the file.
|
||||||
func (cf *ConfFile) WriteFile() error {
|
func (cf *ConfFile) WriteFile() error {
|
||||||
|
if err := cf.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
tmp := cf.filename + ".tmp"
|
tmp := cf.filename + ".tmp"
|
||||||
// 0400 permissions: gocryptfs.conf should be kept secret and never be written to.
|
// 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)
|
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)
|
testKnownFlags = append(testKnownFlags, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cf ConfFile
|
|
||||||
for _, f := range testKnownFlags {
|
for _, f := range testKnownFlags {
|
||||||
if !cf.isFeatureFlagKnown(f) {
|
if !isFeatureFlagKnown(f) {
|
||||||
t.Errorf("flag %q should be known", f)
|
t.Errorf("flag %q should be known", f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f := "StrangeFeatureFlag"
|
f := "StrangeFeatureFlag"
|
||||||
if cf.isFeatureFlagKnown(f) {
|
if isFeatureFlagKnown(f) {
|
||||||
t.Errorf("flag %q should be NOT known", f)
|
t.Errorf("flag %q should be NOT known", f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ const (
|
|||||||
// This flag is mandatory since gocryptfs v1.0.
|
// This flag is mandatory since gocryptfs v1.0.
|
||||||
FlagEMENames
|
FlagEMENames
|
||||||
// FlagGCMIV128 indicates 128-bit GCM IVs.
|
// 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
|
FlagGCMIV128
|
||||||
// FlagLongNames allows file names longer than 176 bytes.
|
// FlagLongNames allows file names longer than 176 bytes.
|
||||||
FlagLongNames
|
FlagLongNames
|
||||||
@ -46,20 +47,8 @@ var knownFlags = map[flagIota]string{
|
|||||||
FlagXChaCha20Poly1305: "XChaCha20Poly1305",
|
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.
|
// isFeatureFlagKnown verifies that we understand a feature flag.
|
||||||
func (cf *ConfFile) isFeatureFlagKnown(flag string) bool {
|
func isFeatureFlagKnown(flag string) bool {
|
||||||
for _, knownFlag := range knownFlags {
|
for _, knownFlag := range knownFlags {
|
||||||
if knownFlag == flag {
|
if knownFlag == flag {
|
||||||
return true
|
return true
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package configfile
|
package configfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@ -62,8 +63,10 @@ func NewScryptKDF(logN int) ScryptKDF {
|
|||||||
|
|
||||||
// DeriveKey returns a new key from a supplied password.
|
// DeriveKey returns a new key from a supplied password.
|
||||||
func (s *ScryptKDF) DeriveKey(pw []byte) []byte {
|
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)
|
k, err := scrypt.Key(pw, s.Salt, s.N, s.R, s.P, s.KeyLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("DeriveKey failed: %v", err)
|
log.Panicf("DeriveKey failed: %v", err)
|
||||||
@ -81,26 +84,22 @@ func (s *ScryptKDF) LogN() int {
|
|||||||
// If not, it exists with an error message.
|
// If not, it exists with an error message.
|
||||||
// This makes sure we do not get weak parameters passed through a
|
// This makes sure we do not get weak parameters passed through a
|
||||||
// rougue gocryptfs.conf.
|
// rougue gocryptfs.conf.
|
||||||
func (s *ScryptKDF) validateParams() {
|
func (s *ScryptKDF) validateParams() error {
|
||||||
minN := 1 << scryptMinLogN
|
minN := 1 << scryptMinLogN
|
||||||
if s.N < minN {
|
if s.N < minN {
|
||||||
tlog.Fatal.Println("Fatal: scryptn below 10 is too low to make sense")
|
return fmt.Errorf("Fatal: scryptn below 10 is too low to make sense")
|
||||||
os.Exit(exitcodes.ScryptParams)
|
|
||||||
}
|
}
|
||||||
if s.R < scryptMinR {
|
if s.R < scryptMinR {
|
||||||
tlog.Fatal.Printf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
|
return fmt.Errorf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
|
||||||
os.Exit(exitcodes.ScryptParams)
|
|
||||||
}
|
}
|
||||||
if s.P < scryptMinP {
|
if s.P < scryptMinP {
|
||||||
tlog.Fatal.Printf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
|
return fmt.Errorf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
|
||||||
os.Exit(exitcodes.ScryptParams)
|
|
||||||
}
|
}
|
||||||
if len(s.Salt) < scryptMinSaltLen {
|
if len(s.Salt) < scryptMinSaltLen {
|
||||||
tlog.Fatal.Printf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
|
return fmt.Errorf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
|
||||||
os.Exit(exitcodes.ScryptParams)
|
|
||||||
}
|
}
|
||||||
if s.KeyLen < cryptocore.KeyLen {
|
if s.KeyLen < cryptocore.KeyLen {
|
||||||
tlog.Fatal.Printf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
|
return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
|
||||||
os.Exit(exitcodes.ScryptParams)
|
|
||||||
}
|
}
|
||||||
|
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