diff options
| -rw-r--r-- | cli_args.go | 4 | ||||
| -rw-r--r-- | internal/configfile/feature_flags.go | 21 | ||||
| -rw-r--r-- | internal/cryptocore/cryptocore.go | 30 | ||||
| -rw-r--r-- | internal/cryptocore/hkdf.go | 7 | ||||
| -rw-r--r-- | mount.go | 22 | 
5 files changed, 68 insertions, 16 deletions
| diff --git a/cli_args.go b/cli_args.go index d082113..3eb92b2 100644 --- a/cli_args.go +++ b/cli_args.go @@ -30,7 +30,8 @@ type argContainer struct {  	plaintextnames, quiet, nosyslog, wpanic,  	longnames, allow_other, reverse, aessiv, nonempty, raw64,  	noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, -	sharedstorage, devrandom, fsck, one_file_system, deterministic_names bool +	sharedstorage, devrandom, fsck, one_file_system, deterministic_names, +	xchacha bool  	// Mount options with opposites  	dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool  	masterkey, mountpoint, cipherdir, cpuprofile, @@ -180,6 +181,7 @@ func parseCliOpts(osArgs []string) (args argContainer) {  	flagSet.BoolVar(&args.fsck, "fsck", false, "Run a filesystem check on CIPHERDIR")  	flagSet.BoolVar(&args.one_file_system, "one-file-system", false, "Don't cross filesystem boundaries")  	flagSet.BoolVar(&args.deterministic_names, "deterministic-names", false, "Disable diriv file name randomisation") +	flagSet.BoolVar(&args.xchacha, "xchacha", false, "Use XChaCha20-Poly1305 file content encryption")  	// Mount options with opposites  	flagSet.BoolVar(&args.dev, "dev", false, "Allow device files") diff --git a/internal/configfile/feature_flags.go b/internal/configfile/feature_flags.go index 45e1853..be5616f 100644 --- a/internal/configfile/feature_flags.go +++ b/internal/configfile/feature_flags.go @@ -28,19 +28,22 @@ const (  	// FlagFIDO2 means that "-fido2" was used when creating the filesystem.  	// The masterkey is protected using a FIDO2 token instead of a password.  	FlagFIDO2 +	// FlagXChaCha20Poly1305 means we use XChaCha20-Poly1305 file content encryption +	FlagXChaCha20Poly1305  )  // knownFlags stores the known feature flags and their string representation  var knownFlags = map[flagIota]string{ -	FlagPlaintextNames: "PlaintextNames", -	FlagDirIV:          "DirIV", -	FlagEMENames:       "EMENames", -	FlagGCMIV128:       "GCMIV128", -	FlagLongNames:      "LongNames", -	FlagAESSIV:         "AESSIV", -	FlagRaw64:          "Raw64", -	FlagHKDF:           "HKDF", -	FlagFIDO2:          "FIDO2", +	FlagPlaintextNames:    "PlaintextNames", +	FlagDirIV:             "DirIV", +	FlagEMENames:          "EMENames", +	FlagGCMIV128:          "GCMIV128", +	FlagLongNames:         "LongNames", +	FlagAESSIV:            "AESSIV", +	FlagRaw64:             "Raw64", +	FlagHKDF:              "HKDF", +	FlagFIDO2:             "FIDO2", +	FlagXChaCha20Poly1305: "XChaCha20Poly1305",  }  // Filesystems that do not have these feature flags set are deprecated. diff --git a/internal/cryptocore/cryptocore.go b/internal/cryptocore/cryptocore.go index 5da88db..3e6f5e8 100644 --- a/internal/cryptocore/cryptocore.go +++ b/internal/cryptocore/cryptocore.go @@ -10,6 +10,8 @@ import (  	"log"  	"runtime" +	"golang.org/x/crypto/chacha20poly1305" +  	"github.com/rfjakob/eme"  	"github.com/rfjakob/gocryptfs/v2/internal/siv_aead" @@ -29,11 +31,17 @@ type AEADTypeEnum int  const (  	// BackendOpenSSL specifies the OpenSSL backend. +	// "AES-GCM-256-OpenSSL" in gocryptfs -speed.  	BackendOpenSSL AEADTypeEnum = 3  	// BackendGoGCM specifies the Go based GCM backend. +	// "AES-GCM-256-Go" in gocryptfs -speed.  	BackendGoGCM AEADTypeEnum = 4  	// BackendAESSIV specifies an AESSIV backend. +	// "AES-SIV-512-Go" in gocryptfs -speed.  	BackendAESSIV AEADTypeEnum = 5 +	// BackendXChaCha20Poly1305 specifies XChaCha20-Poly1305-Go. +	// "XChaCha20-Poly1305-Go" in gocryptfs -speed. +	BackendXChaCha20Poly1305 AEADTypeEnum = 6  )  func (a AEADTypeEnum) String() string { @@ -78,7 +86,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec  	if len(key) != KeyLen {  		log.Panicf("Unsupported key length of %d bytes", len(key))  	} -	if IVBitLen != 96 && IVBitLen != 128 { +	if IVBitLen != 96 && IVBitLen != 128 && IVBitLen != chacha20poly1305.NonceSizeX*8 {  		log.Panicf("Unsupported IV length of %d bits", IVBitLen)  	} @@ -152,8 +160,26 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec  		for i := range key64 {  			key64[i] = 0  		} +	} else if aeadType == BackendXChaCha20Poly1305 { +		// We don't support legacy modes with XChaCha20-Poly1305 +		if IVBitLen != chacha20poly1305.NonceSizeX*8 { +			log.Panicf("XChaCha20-Poly1305 must use 192-bit IVs, you wanted %d", IVBitLen) +		} +		if !useHKDF { +			log.Panic("XChaCha20-Poly1305 must use HKDF, but it is disabled") +		} +		derivedKey := hkdfDerive(key, hkdfInfoXChaChaPoly1305Content, chacha20poly1305.KeySize) +		aeadCipher, err = chacha20poly1305.NewX(derivedKey) +		if err != nil { +			log.Panic(err) +		}  	} else { -		log.Panic("unknown backend cipher") +		log.Panicf("unknown cipher backend %q", aeadType.String()) +	} + +	if aeadCipher.NonceSize()*8 != IVBitLen { +		log.Panicf("Mismatched aeadCipher.NonceSize*8=%d and IVBitLen=%d bits", +			aeadCipher.NonceSize()*8, IVBitLen)  	}  	return &CryptoCore{ diff --git a/internal/cryptocore/hkdf.go b/internal/cryptocore/hkdf.go index 87ca1b9..b56f507 100644 --- a/internal/cryptocore/hkdf.go +++ b/internal/cryptocore/hkdf.go @@ -10,9 +10,10 @@ import (  const (  	// "info" data that HKDF mixes into the generated key to make it unique.  	// For convenience, we use a readable string. -	hkdfInfoEMENames   = "EME filename encryption" -	hkdfInfoGCMContent = "AES-GCM file content encryption" -	hkdfInfoSIVContent = "AES-SIV file content encryption" +	hkdfInfoEMENames               = "EME filename encryption" +	hkdfInfoGCMContent             = "AES-GCM file content encryption" +	hkdfInfoSIVContent             = "AES-SIV file content encryption" +	hkdfInfoXChaChaPoly1305Content = "XChaCha20-Poly1305 file content encryption"  )  // hkdfDerive derives "outLen" bytes from "masterkey" and "info" using @@ -19,6 +19,8 @@ import (  	"syscall"  	"time" +	"golang.org/x/crypto/chacha20poly1305" +  	"github.com/hanwen/go-fuse/v2/fs"  	"github.com/hanwen/go-fuse/v2/fuse" @@ -249,12 +251,17 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f  	// Reconciliate CLI and config file arguments into a fusefrontend.Args struct  	// that is passed to the filesystem implementation  	cryptoBackend := cryptocore.BackendGoGCM +	IVBits := contentenc.DefaultIVBits  	if args.openssl {  		cryptoBackend = cryptocore.BackendOpenSSL  	}  	if args.aessiv {  		cryptoBackend = cryptocore.BackendAESSIV  	} +	if args.xchacha { +		cryptoBackend = cryptocore.BackendXChaCha20Poly1305 +		IVBits = chacha20poly1305.NonceSizeX * 8 +	}  	// forceOwner implies allow_other, as documented.  	// Set this early, so args.allow_other can be relied on below this point.  	if args._forceOwner != nil { @@ -287,10 +294,23 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f  		args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF)  		if confFile.IsFeatureFlagSet(configfile.FlagAESSIV) {  			cryptoBackend = cryptocore.BackendAESSIV +			IVBits = contentenc.DefaultIVBits  		} else if args.reverse {  			tlog.Fatal.Printf("AES-SIV is required by reverse mode, but not enabled in the config file")  			os.Exit(exitcodes.Usage)  		} +		if confFile.IsFeatureFlagSet(configfile.FlagXChaCha20Poly1305) { +			cryptoBackend = cryptocore.BackendXChaCha20Poly1305 +			IVBits = chacha20poly1305.NonceSizeX * 8 +		} +		// If neither AES-SIV nor XChaCha are selected, we must be using AES-GCM +		if !confFile.IsFeatureFlagSet(configfile.FlagAESSIV) && !confFile.IsFeatureFlagSet(configfile.FlagXChaCha20Poly1305) { +			cryptoBackend = cryptocore.BackendGoGCM +			if args.openssl { +				cryptoBackend = cryptocore.BackendOpenSSL +			} +			IVBits = contentenc.DefaultIVBits +		}  	}  	// If allow_other is set and we run as root, try to give newly created files to  	// the right user. @@ -299,7 +319,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f  	}  	// Init crypto backend -	cCore := cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, args.hkdf, args.forcedecode) +	cCore := cryptocore.New(masterkey, cryptoBackend, IVBits, args.hkdf, args.forcedecode)  	cEnc := contentenc.New(cCore, contentenc.DefaultBS, args.forcedecode)  	nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames,  		args.raw64, []string(args.badname), frontendArgs.DeterministicNames) | 
