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