Add partial XChaCha20-Poly1305 support (mount flag only)

Mount flag only at the moment, not saved to gocryptfs.conf.

https://github.com/rfjakob/gocryptfs/issues/452
This commit is contained in:
Jakob Unterwurzacher 2021-08-21 12:08:37 +02:00
parent b02812f8b3
commit 4764a9bde0
5 changed files with 68 additions and 16 deletions

View File

@ -30,7 +30,8 @@ type argContainer struct {
plaintextnames, quiet, nosyslog, wpanic, plaintextnames, quiet, nosyslog, wpanic,
longnames, allow_other, reverse, aessiv, nonempty, raw64, longnames, allow_other, reverse, aessiv, nonempty, raw64,
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, 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 // Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
masterkey, mountpoint, cipherdir, cpuprofile, 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.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.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.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 // Mount options with opposites
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files") flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")

View File

@ -28,19 +28,22 @@ const (
// FlagFIDO2 means that "-fido2" was used when creating the filesystem. // FlagFIDO2 means that "-fido2" was used when creating the filesystem.
// The masterkey is protected using a FIDO2 token instead of a password. // The masterkey is protected using a FIDO2 token instead of a password.
FlagFIDO2 FlagFIDO2
// FlagXChaCha20Poly1305 means we use XChaCha20-Poly1305 file content encryption
FlagXChaCha20Poly1305
) )
// knownFlags stores the known feature flags and their string representation // knownFlags stores the known feature flags and their string representation
var knownFlags = map[flagIota]string{ var knownFlags = map[flagIota]string{
FlagPlaintextNames: "PlaintextNames", FlagPlaintextNames: "PlaintextNames",
FlagDirIV: "DirIV", FlagDirIV: "DirIV",
FlagEMENames: "EMENames", FlagEMENames: "EMENames",
FlagGCMIV128: "GCMIV128", FlagGCMIV128: "GCMIV128",
FlagLongNames: "LongNames", FlagLongNames: "LongNames",
FlagAESSIV: "AESSIV", FlagAESSIV: "AESSIV",
FlagRaw64: "Raw64", FlagRaw64: "Raw64",
FlagHKDF: "HKDF", FlagHKDF: "HKDF",
FlagFIDO2: "FIDO2", FlagFIDO2: "FIDO2",
FlagXChaCha20Poly1305: "XChaCha20Poly1305",
} }
// Filesystems that do not have these feature flags set are deprecated. // Filesystems that do not have these feature flags set are deprecated.

View File

@ -10,6 +10,8 @@ import (
"log" "log"
"runtime" "runtime"
"golang.org/x/crypto/chacha20poly1305"
"github.com/rfjakob/eme" "github.com/rfjakob/eme"
"github.com/rfjakob/gocryptfs/v2/internal/siv_aead" "github.com/rfjakob/gocryptfs/v2/internal/siv_aead"
@ -29,11 +31,17 @@ type AEADTypeEnum int
const ( const (
// BackendOpenSSL specifies the OpenSSL backend. // BackendOpenSSL specifies the OpenSSL backend.
// "AES-GCM-256-OpenSSL" in gocryptfs -speed.
BackendOpenSSL AEADTypeEnum = 3 BackendOpenSSL AEADTypeEnum = 3
// BackendGoGCM specifies the Go based GCM backend. // BackendGoGCM specifies the Go based GCM backend.
// "AES-GCM-256-Go" in gocryptfs -speed.
BackendGoGCM AEADTypeEnum = 4 BackendGoGCM AEADTypeEnum = 4
// BackendAESSIV specifies an AESSIV backend. // BackendAESSIV specifies an AESSIV backend.
// "AES-SIV-512-Go" in gocryptfs -speed.
BackendAESSIV AEADTypeEnum = 5 BackendAESSIV AEADTypeEnum = 5
// BackendXChaCha20Poly1305 specifies XChaCha20-Poly1305-Go.
// "XChaCha20-Poly1305-Go" in gocryptfs -speed.
BackendXChaCha20Poly1305 AEADTypeEnum = 6
) )
func (a AEADTypeEnum) String() string { func (a AEADTypeEnum) String() string {
@ -78,7 +86,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
if len(key) != KeyLen { if len(key) != KeyLen {
log.Panicf("Unsupported key length of %d bytes", len(key)) 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) 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 { for i := range key64 {
key64[i] = 0 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 { } 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{ return &CryptoCore{

View File

@ -10,9 +10,10 @@ import (
const ( const (
// "info" data that HKDF mixes into the generated key to make it unique. // "info" data that HKDF mixes into the generated key to make it unique.
// For convenience, we use a readable string. // For convenience, we use a readable string.
hkdfInfoEMENames = "EME filename encryption" hkdfInfoEMENames = "EME filename encryption"
hkdfInfoGCMContent = "AES-GCM file content encryption" hkdfInfoGCMContent = "AES-GCM file content encryption"
hkdfInfoSIVContent = "AES-SIV file content encryption" hkdfInfoSIVContent = "AES-SIV file content encryption"
hkdfInfoXChaChaPoly1305Content = "XChaCha20-Poly1305 file content encryption"
) )
// hkdfDerive derives "outLen" bytes from "masterkey" and "info" using // hkdfDerive derives "outLen" bytes from "masterkey" and "info" using

View File

@ -19,6 +19,8 @@ import (
"syscall" "syscall"
"time" "time"
"golang.org/x/crypto/chacha20poly1305"
"github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse" "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 // Reconciliate CLI and config file arguments into a fusefrontend.Args struct
// that is passed to the filesystem implementation // that is passed to the filesystem implementation
cryptoBackend := cryptocore.BackendGoGCM cryptoBackend := cryptocore.BackendGoGCM
IVBits := contentenc.DefaultIVBits
if args.openssl { if args.openssl {
cryptoBackend = cryptocore.BackendOpenSSL cryptoBackend = cryptocore.BackendOpenSSL
} }
if args.aessiv { if args.aessiv {
cryptoBackend = cryptocore.BackendAESSIV cryptoBackend = cryptocore.BackendAESSIV
} }
if args.xchacha {
cryptoBackend = cryptocore.BackendXChaCha20Poly1305
IVBits = chacha20poly1305.NonceSizeX * 8
}
// forceOwner implies allow_other, as documented. // forceOwner implies allow_other, as documented.
// Set this early, so args.allow_other can be relied on below this point. // Set this early, so args.allow_other can be relied on below this point.
if args._forceOwner != nil { if args._forceOwner != nil {
@ -287,10 +294,23 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF) args.hkdf = confFile.IsFeatureFlagSet(configfile.FlagHKDF)
if confFile.IsFeatureFlagSet(configfile.FlagAESSIV) { if confFile.IsFeatureFlagSet(configfile.FlagAESSIV) {
cryptoBackend = cryptocore.BackendAESSIV cryptoBackend = cryptocore.BackendAESSIV
IVBits = contentenc.DefaultIVBits
} else if args.reverse { } else if args.reverse {
tlog.Fatal.Printf("AES-SIV is required by reverse mode, but not enabled in the config file") tlog.Fatal.Printf("AES-SIV is required by reverse mode, but not enabled in the config file")
os.Exit(exitcodes.Usage) 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 // If allow_other is set and we run as root, try to give newly created files to
// the right user. // the right user.
@ -299,7 +319,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f
} }
// Init crypto backend // 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) cEnc := contentenc.New(cCore, contentenc.DefaultBS, args.forcedecode)
nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames, nameTransform := nametransform.New(cCore.EMECipher, frontendArgs.LongNames,
args.raw64, []string(args.badname), frontendArgs.DeterministicNames) args.raw64, []string(args.badname), frontendArgs.DeterministicNames)