libgocryptfs: update to gocryptfs v2.1

This commit is contained in:
Matéo Duparc 2021-08-29 12:46:32 +02:00
commit f0e45c7b7e
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
22 changed files with 278 additions and 378 deletions

View File

@ -60,7 +60,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
var cachedIV []byte var cachedIV []byte
if !OpenedVolumes[sessionID].plainTextNames { if !OpenedVolumes[sessionID].plainTextNames {
// Read the DirIV from disk // Read the DirIV from disk
cachedIV, err = nametransform.ReadDirIVAt(fd) cachedIV, err = volume.nameTransform.ReadDirIVAt(fd)
if err != nil { if err != nil {
return nil, nil, 0 return nil, nil, 0
} }

View File

@ -50,7 +50,7 @@ func (volume *Volume) openBackingDir(relPath string) (dirfd int, cName string, e
// Walk the directory tree // Walk the directory tree
parts := strings.Split(relPath, "/") parts := strings.Split(relPath, "/")
for i, name := range parts { for i, name := range parts {
iv, err := nametransform.ReadDirIVAt(dirfd) iv, err := volume.nameTransform.ReadDirIVAt(dirfd)
if err != nil { if err != nil {
syscall.Close(dirfd) syscall.Close(dirfd)
return -1, "", err return -1, "", err
@ -112,7 +112,7 @@ func (volume *Volume) prepareAtSyscall(path string) (dirfd int, cName string, er
// Cache store // Cache store
if !volume.plainTextNames { if !volume.plainTextNames {
// TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work? // TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work?
iv, err := nametransform.ReadDirIVAt(dirfd) iv, err := volume.nameTransform.ReadDirIVAt(dirfd)
if err != nil { if err != nil {
syscall.Close(dirfd) syscall.Close(dirfd)
return -1, "", err return -1, "", err

View File

@ -5,9 +5,7 @@ package configfile
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log"
"syscall" "syscall"
"os" "os"
@ -60,64 +58,66 @@ type ConfFile struct {
filename string filename string
} }
// randBytesDevRandom gets "n" random bytes from /dev/random or panics // CreateArgs exists because the argument list to Create became too long.
func randBytesDevRandom(n int) []byte { type CreateArgs struct {
f, err := os.Open("/dev/random") Filename string
if err != nil { Password []byte
log.Panic("Failed to open /dev/random: " + err.Error()) PlaintextNames bool
} LogN int
defer f.Close() Creator string
b := make([]byte, n) AESSIV bool
_, err = io.ReadFull(f, b) DeterministicNames bool
if err != nil { XChaCha20Poly1305 bool
log.Panic("Failed to read random bytes: " + err.Error())
}
return b
} }
// 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(filename string, password []byte, plaintextNames bool, func Create(args *CreateArgs) error {
logN int, creator string, aessiv bool, devrandom bool, fido2CredentialID []byte, fido2HmacSalt []byte) error { cf := ConfFile{
var cf ConfFile filename: args.Filename,
cf.filename = filename Creator: args.Creator,
cf.Creator = creator Version: contentenc.CurrentVersion,
cf.Version = contentenc.CurrentVersion }
// Feature flags
// Set feature flags cf.setFeatureFlag(FlagHKDF)
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMIV128]) if args.XChaCha20Poly1305 {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagHKDF]) cf.setFeatureFlag(FlagXChaCha20Poly1305)
if plaintextNames {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagPlaintextNames])
} else { } else {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagDirIV]) // 128-bit IVs are mandatory for AES-GCM (default is 96!) and AES-SIV,
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames]) // XChaCha20Poly1305 uses even an even longer IV of 192 bits.
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames]) cf.setFeatureFlag(FlagGCMIV128)
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagRaw64])
} }
if aessiv { if args.PlaintextNames {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV]) cf.setFeatureFlag(FlagPlaintextNames)
} } else {
if len(fido2CredentialID) > 0 { if !args.DeterministicNames {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2]) cf.setFeatureFlag(FlagDirIV)
cf.FIDO2 = &FIDO2Params{
CredentialID: fido2CredentialID,
HMACSalt: fido2HmacSalt,
} }
cf.setFeatureFlag(FlagEMENames)
cf.setFeatureFlag(FlagLongNames)
cf.setFeatureFlag(FlagRaw64)
}
if args.AESSIV {
cf.setFeatureFlag(FlagAESSIV)
}
// Catch bugs and invalid cli flag combinations early
cf.ScryptObject = NewScryptKDF(args.LogN)
if err := cf.Validate(); err != nil {
return err
}
// 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 key := cryptocore.RandBytes(cryptocore.KeyLen)
if devrandom {
key = randBytesDevRandom(cryptocore.KeyLen)
} else {
key = cryptocore.RandBytes(cryptocore.KeyLen)
}
// Encrypt it using the password // Encrypt it using the password
// This sets ScryptObject and EncryptedKey // This sets ScryptObject and EncryptedKey
// Note: this looks at the FeatureFlags, so call it AFTER setting them. // Note: this looks at the FeatureFlags, so call it AFTER setting them.
cf.EncryptKey(key, password, logN, false) cf.EncryptKey(key, args.Password, args.LogN, false)
for i := range key { for i := range key {
key[i] = 0 key[i] = 0
} }
@ -175,39 +175,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 {
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, giveHash bool) (masterkey, scryptHash []byte, err error) { func (cf *ConfFile) DecryptMasterKey(password []byte, giveHash bool) (masterkey, scryptHash []byte, err error) {
@ -296,6 +279,9 @@ func (cf *ConfFile) GetMasterkey(password, givenScryptHash, returnedScryptHashBu
// 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)
@ -315,7 +301,7 @@ func (cf *ConfFile) WriteFile() error {
err = fd.Sync() err = fd.Sync()
if err != nil { if err != nil {
// This can happen on network drives: FRITZ.NAS mounted on MacOS returns // This can happen on network drives: FRITZ.NAS mounted on MacOS returns
// "operation not supported": https://github.com/rfjakob/gocryptfs/issues/390 // "operation not supported": https://github.com/rfjakob/gocryptfs/v2/issues/390
// Try sync instead // Try sync instead
syscall.Sync() syscall.Sync()
} }
@ -340,3 +326,18 @@ func getKeyEncrypter(scryptHash []byte, useHKDF bool) *contentenc.ContentEnc {
ce := contentenc.New(cc, 4096, false) ce := contentenc.New(cc, 4096, false)
return ce return ce
} }
// ContentEncryption tells us which content encryption algorithm is selected
func (cf *ConfFile) ContentEncryption() (algo cryptocore.AEADTypeEnum, err error) {
if err := cf.Validate(); err != nil {
return cryptocore.AEADTypeEnum{}, err
}
if cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) {
return cryptocore.BackendXChaCha20Poly1305, nil
}
if cf.IsFeatureFlagSet(FlagAESSIV) {
return cryptocore.BackendAESSIV, nil
}
// If neither AES-SIV nor XChaCha are selected, we must be using AES-GCM
return cryptocore.BackendGoGCM, nil
}

View File

@ -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
@ -28,36 +29,26 @@ 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.
var requiredFlagsNormal = []flagIota{
FlagDirIV,
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

View File

@ -1,6 +1,7 @@
package configfile package configfile
import ( import (
"fmt"
"log" "log"
"math" "math"
"os" "os"
@ -61,8 +62,9 @@ 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 {
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)
@ -80,21 +82,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 {
os.Exit(exitcodes.ScryptParams) return fmt.Errorf("Fatal: scryptn below 10 is too low to make sense")
} }
if s.R < scryptMinR { if 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 { if 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 { if 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 { if 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"
"../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
}

View File

@ -13,9 +13,6 @@ import (
"../stupidgcm" "../stupidgcm"
) )
// NonceMode determines how nonces are created.
type NonceMode int
const ( const (
//value from FUSE doc //value from FUSE doc
MAX_KERNEL_WRITE = 128 * 1024 MAX_KERNEL_WRITE = 128 * 1024
@ -27,15 +24,6 @@ const (
// master key in the config file is encrypted with a 96-bit IV for // master key in the config file is encrypted with a 96-bit IV for
// gocryptfs v1.2 and earlier. v1.3 switched to 128 bit. // gocryptfs v1.2 and earlier. v1.3 switched to 128 bit.
DefaultIVBits = 128 DefaultIVBits = 128
_ = iota // skip zero
// RandomNonce chooses a random nonce.
RandomNonce NonceMode = iota
// ReverseDeterministicNonce chooses a deterministic nonce, suitable for
// use in reverse mode.
ReverseDeterministicNonce NonceMode = iota
// ExternalNonce derives a nonce from external sources.
ExternalNonce NonceMode = iota
) )
// ContentEnc is used to encipher and decipher file content. // ContentEnc is used to encipher and decipher file content.
@ -171,7 +159,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
nonce := ciphertext[:be.cryptoCore.IVLen] nonce := ciphertext[:be.cryptoCore.IVLen]
if bytes.Equal(nonce, be.allZeroNonce) { if bytes.Equal(nonce, be.allZeroNonce) {
// Bug in tmpfs? // Bug in tmpfs?
// https://github.com/rfjakob/gocryptfs/issues/56 // https://github.com/rfjakob/gocryptfs/v2/issues/56
// http://www.spinics.net/lists/kernel/msg2370127.html // http://www.spinics.net/lists/kernel/msg2370127.html
return nil, errors.New("all-zero nonce") return nil, errors.New("all-zero nonce")
} }

View File

@ -6,10 +6,11 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/sha512" "crypto/sha512"
"fmt"
"log" "log"
"runtime" "runtime"
"golang.org/x/crypto/chacha20poly1305"
"github.com/rfjakob/eme" "github.com/rfjakob/eme"
"../siv_aead" "../siv_aead"
@ -17,37 +18,35 @@ import (
) )
const ( const (
// KeyLen is the cipher key length in bytes. 32 for AES-256. // KeyLen is the cipher key length in bytes. All backends use 32 bytes.
KeyLen = 32 KeyLen = 32
// AuthTagLen is the length of a GCM auth tag in bytes. // AuthTagLen is the length of a authentication tag in bytes.
// All backends use 16 bytes.
AuthTagLen = 16 AuthTagLen = 16
) )
// AEADTypeEnum indicates the type of AEAD backend in use. // AEADTypeEnum indicates the type of AEAD backend in use.
type AEADTypeEnum int type AEADTypeEnum struct {
Name string
const ( NonceSize int
// BackendOpenSSL specifies the OpenSSL backend.
BackendOpenSSL AEADTypeEnum = 3
// BackendGoGCM specifies the Go based GCM backend.
BackendGoGCM AEADTypeEnum = 4
// BackendAESSIV specifies an AESSIV backend.
BackendAESSIV AEADTypeEnum = 5
)
func (a AEADTypeEnum) String() string {
switch a {
case BackendOpenSSL:
return "BackendOpenSSL"
case BackendGoGCM:
return "BackendGoGCM"
case BackendAESSIV:
return "BackendAESSIV"
default:
return fmt.Sprintf("%d", a)
}
} }
// BackendOpenSSL specifies the OpenSSL backend.
// "AES-GCM-256-OpenSSL" in gocryptfs -speed.
var BackendOpenSSL AEADTypeEnum = AEADTypeEnum{"AES-GCM-256-OpenSSL", 16}
// BackendGoGCM specifies the Go based GCM backend.
// "AES-GCM-256-Go" in gocryptfs -speed.
var BackendGoGCM AEADTypeEnum = AEADTypeEnum{"AES-GCM-256-Go", 16}
// BackendAESSIV specifies an AESSIV backend.
// "AES-SIV-512-Go" in gocryptfs -speed.
var BackendAESSIV AEADTypeEnum = AEADTypeEnum{"AES-SIV-512-Go", siv_aead.NonceSize}
// BackendXChaCha20Poly1305 specifies XChaCha20-Poly1305-Go.
// "XChaCha20-Poly1305-Go" in gocryptfs -speed.
var BackendXChaCha20Poly1305 AEADTypeEnum = AEADTypeEnum{"XChaCha20-Poly1305-Go", chacha20poly1305.NonceSizeX}
// CryptoCore is the low level crypto implementation. // CryptoCore is the low level crypto implementation.
type CryptoCore struct { type CryptoCore struct {
// EME is used for filename encryption. // EME is used for filename encryption.
@ -58,7 +57,8 @@ type CryptoCore struct {
AEADBackend AEADTypeEnum AEADBackend AEADTypeEnum
// GCM needs unique IVs (nonces) // GCM needs unique IVs (nonces)
IVGenerator *nonceGenerator IVGenerator *nonceGenerator
IVLen int // IVLen in bytes
IVLen int
} }
// New returns a new CryptoCore object or panics. // New returns a new CryptoCore object or panics.
@ -71,10 +71,11 @@ type CryptoCore struct {
// a config file) or the masterkey (when finally mounting the filesystem). // a config file) or the masterkey (when finally mounting the filesystem).
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDecode bool) *CryptoCore { func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDecode bool) *CryptoCore {
if len(key) != KeyLen { if len(key) != KeyLen {
log.Panic(fmt.Sprintf("Unsupported key length %d", len(key))) log.Panicf("Unsupported key length of %d bytes", len(key))
}
if IVBitLen != 96 && IVBitLen != 128 && IVBitLen != chacha20poly1305.NonceSizeX*8 {
log.Panicf("Unsupported IV length of %d bits", IVBitLen)
} }
// We want the IV size in bytes
IVLen := IVBitLen / 8
// Initialize EME for filename encryption. // Initialize EME for filename encryption.
var emeCipher *eme.EMECipher var emeCipher *eme.EMECipher
@ -103,12 +104,14 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
if useHKDF { if useHKDF {
gcmKey = hkdfDerive(key, hkdfInfoGCMContent, KeyLen) gcmKey = hkdfDerive(key, hkdfInfoGCMContent, KeyLen)
} else { } else {
// Filesystems created by gocryptfs v0.7 through v1.2 don't use HKDF.
// Example: tests/example_filesystems/v0.9
gcmKey = append([]byte{}, key...) gcmKey = append([]byte{}, key...)
} }
switch aeadType { switch aeadType {
case BackendOpenSSL: case BackendOpenSSL:
if IVLen != 16 { if IVBitLen != 128 {
log.Panic("stupidgcm only supports 128-bit IVs") log.Panicf("stupidgcm only supports 128-bit IVs, you wanted %d", IVBitLen)
} }
aeadCipher = stupidgcm.New(gcmKey, forceDecode) aeadCipher = stupidgcm.New(gcmKey, forceDecode)
case BackendGoGCM: case BackendGoGCM:
@ -116,7 +119,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
aeadCipher, err = cipher.NewGCMWithNonceSize(goGcmBlockCipher, IVLen) aeadCipher, err = cipher.NewGCMWithNonceSize(goGcmBlockCipher, IVBitLen/8)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
@ -125,9 +128,9 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
gcmKey[i] = 0 gcmKey[i] = 0
} }
} else if aeadType == BackendAESSIV { } else if aeadType == BackendAESSIV {
if IVLen != 16 { if IVBitLen != 128 {
// SIV supports any nonce size, but we only use 16. // SIV supports any nonce size, but we only use 128.
log.Panic("AES-SIV must use 16-byte nonces") log.Panicf("AES-SIV must use 128-bit IVs, you wanted %d", IVBitLen)
} }
// AES-SIV uses 1/2 of the key for authentication, 1/2 for // AES-SIV uses 1/2 of the key for authentication, 1/2 for
// encryption, so we need a 64-bytes key for AES-256. Derive it from // encryption, so we need a 64-bytes key for AES-256. Derive it from
@ -144,16 +147,34 @@ 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.Name)
}
if aeadCipher.NonceSize()*8 != IVBitLen {
log.Panicf("Mismatched aeadCipher.NonceSize*8=%d and IVBitLen=%d bits",
aeadCipher.NonceSize()*8, IVBitLen)
} }
return &CryptoCore{ return &CryptoCore{
EMECipher: emeCipher, EMECipher: emeCipher,
AEADCipher: aeadCipher, AEADCipher: aeadCipher,
AEADBackend: aeadType, AEADBackend: aeadType,
IVGenerator: &nonceGenerator{nonceLen: IVLen}, IVGenerator: &nonceGenerator{nonceLen: IVBitLen / 8},
IVLen: IVLen, IVLen: IVBitLen / 8,
} }
} }

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

@ -22,7 +22,11 @@ const (
// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd". // ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".
// Using the dirfd makes it immune to concurrent renames of the directory. // Using the dirfd makes it immune to concurrent renames of the directory.
// Retries on EINTR. // Retries on EINTR.
func ReadDirIVAt(dirfd int) (iv []byte, err error) { // If deterministicNames is set it returns an all-zero slice.
func (n *NameTransform) ReadDirIVAt(dirfd int) (iv []byte, err error) {
if n.deterministicNames {
return make([]byte, DirIVLen), nil
}
fdRaw, err := syscallcompat.Openat(dirfd, DirIVFilename, fdRaw, err := syscallcompat.Openat(dirfd, DirIVFilename,
syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil { if err != nil {
@ -63,7 +67,7 @@ func WriteDirIVAt(dirfd int) error {
iv := cryptocore.RandBytes(DirIVLen) iv := cryptocore.RandBytes(DirIVLen)
// 0400 permissions: gocryptfs.diriv should never be modified after creation. // 0400 permissions: gocryptfs.diriv should never be modified after creation.
// Don't use "ioutil.WriteFile", it causes trouble on NFS: // Don't use "ioutil.WriteFile", it causes trouble on NFS:
// https://github.com/rfjakob/gocryptfs/commit/7d38f80a78644c8ec4900cc990bfb894387112ed // https://github.com/rfjakob/gocryptfs/v2/commit/7d38f80a78644c8ec4900cc990bfb894387112ed
fd, err := syscallcompat.Openat(dirfd, DirIVFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, dirivPerms) fd, err := syscallcompat.Openat(dirfd, DirIVFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, dirivPerms)
if err != nil { if err != nil {
return err return err

View File

@ -124,7 +124,7 @@ func (n *NameTransform) WriteLongNameAt(dirfd int, hashName string, plainName st
plainName = filepath.Base(plainName) plainName = filepath.Base(plainName)
// Encrypt the basename // Encrypt the basename
dirIV, err := ReadDirIVAt(dirfd) dirIV, err := n.ReadDirIVAt(dirfd)
if err != nil { if err != nil {
return err return err
} }

View File

@ -23,20 +23,22 @@ type NameTransform struct {
// on the Raw64 feature flag // on the Raw64 feature flag
B64 *base64.Encoding B64 *base64.Encoding
// Patterns to bypass decryption // Patterns to bypass decryption
badnamePatterns []string badnamePatterns []string
deterministicNames bool
} }
// New returns a new NameTransform instance. // New returns a new NameTransform instance.
func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string) *NameTransform { func New(e *eme.EMECipher, longNames bool, raw64 bool, badname []string, deterministicNames bool) *NameTransform {
b64 := base64.URLEncoding b64 := base64.URLEncoding
if raw64 { if raw64 {
b64 = base64.RawURLEncoding b64 = base64.RawURLEncoding
} }
return &NameTransform{ return &NameTransform{
emeCipher: e, emeCipher: e,
longNames: longNames, longNames: longNames,
B64: b64, B64: b64,
badnamePatterns: badname, badnamePatterns: badname,
deterministicNames: deterministicNames,
} }
} }

View File

@ -6,14 +6,14 @@ const (
// never chmod'ed or chown'ed. // never chmod'ed or chown'ed.
// //
// Group-readable so the FS can be mounted by several users in the same group // Group-readable so the FS can be mounted by several users in the same group
// (see https://github.com/rfjakob/gocryptfs/issues/387 ). // (see https://github.com/rfjakob/gocryptfs/v2/issues/387 ).
// //
// Note that gocryptfs.conf is still created with 0400 permissions so the // Note that gocryptfs.conf is still created with 0400 permissions so the
// owner must explicitly chmod it to permit access. // owner must explicitly chmod it to permit access.
// //
// World-readable so an encrypted directory can be copied by the non-root // World-readable so an encrypted directory can be copied by the non-root
// owner when gocryptfs is running as root // owner when gocryptfs is running as root
// ( https://github.com/rfjakob/gocryptfs/issues/539 ). // ( https://github.com/rfjakob/gocryptfs/v2/issues/539 ).
dirivPerms = 0444 dirivPerms = 0444
// Permissions for gocryptfs.longname.[sha256].name files. // Permissions for gocryptfs.longname.[sha256].name files.

View File

@ -19,6 +19,11 @@ const (
// KeyLen is the required key length. The SIV algorithm supports other lengths, // KeyLen is the required key length. The SIV algorithm supports other lengths,
// but we only support 64. // but we only support 64.
KeyLen = 64 KeyLen = 64
// NonceSize is the required nonce/IV length.
// SIV supports any nonce size, but in gocryptfs we exclusively use 16.
NonceSize = 16
// Overhead is the number of bytes added for integrity checking
Overhead = 16
) )
// New returns a new cipher.AEAD implementation. // New returns a new cipher.AEAD implementation.
@ -42,11 +47,11 @@ func new2(keyIn []byte) cipher.AEAD {
func (s *sivAead) NonceSize() int { func (s *sivAead) NonceSize() int {
// SIV supports any nonce size, but in gocryptfs we exclusively use 16. // SIV supports any nonce size, but in gocryptfs we exclusively use 16.
return 16 return NonceSize
} }
func (s *sivAead) Overhead() int { func (s *sivAead) Overhead() int {
return 16 return Overhead
} }
// Seal encrypts "in" using "nonce" and "authData" and appends the result to "dst" // Seal encrypts "in" using "nonce" and "authData" and appends the result to "dst"

View File

@ -14,7 +14,7 @@ import (
// 2) Is ARM64 && has AES instructions && Go is v1.11 or higher // 2) Is ARM64 && has AES instructions && Go is v1.11 or higher
// (commit https://github.com/golang/go/commit/4f1f503373cda7160392be94e3849b0c9b9ebbda) // (commit https://github.com/golang/go/commit/4f1f503373cda7160392be94e3849b0c9b9ebbda)
// //
// See https://github.com/rfjakob/gocryptfs/wiki/CPU-Benchmarks // See https://github.com/rfjakob/gocryptfs/v2/wiki/CPU-Benchmarks
// for benchmarks. // for benchmarks.
func PreferOpenSSL() bool { func PreferOpenSSL() bool {
if BuiltWithoutOpenssl { if BuiltWithoutOpenssl {
@ -26,7 +26,7 @@ func PreferOpenSSL() bool {
return false return false
} }
// On the Apple M1, Go stdlib is faster than OpenSSL, despite cpu.ARM64.HasAES // On the Apple M1, Go stdlib is faster than OpenSSL, despite cpu.ARM64.HasAES
// reading false: https://github.com/rfjakob/gocryptfs/issues/556#issuecomment-848079309 // reading false: https://github.com/rfjakob/gocryptfs/v2/issues/556#issuecomment-848079309
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
return false return false
} }

View File

@ -224,7 +224,7 @@ func (g *StupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) {
if res != 1 { if res != 1 {
// The error code must always be checked by the calling function, because the decrypted buffer // The error code must always be checked by the calling function, because the decrypted buffer
// may contain corrupted data that we are returning in case the user forced reads // may contain corrupted data that we are returning in case the user forced reads
if g.forceDecode == true { if g.forceDecode {
return append(dst, buf...), ErrAuth return append(dst, buf...), ErrAuth
} }
return nil, ErrAuth return nil, ErrAuth

View File

@ -1,52 +0,0 @@
// +build without_openssl
package stupidgcm
import (
"fmt"
"os"
"../exitcodes"
)
type StupidGCM struct{}
const (
// BuiltWithoutOpenssl indicates if openssl been disabled at compile-time
BuiltWithoutOpenssl = true
)
func errExit() {
fmt.Fprintln(os.Stderr, "gocryptfs has been compiled without openssl support but you are still trying to use openssl")
os.Exit(exitcodes.OpenSSL)
}
func New(_ []byte, _ bool) *StupidGCM {
errExit()
// Never reached
return &StupidGCM{}
}
func (g *StupidGCM) NonceSize() int {
errExit()
return -1
}
func (g *StupidGCM) Overhead() int {
errExit()
return -1
}
func (g *StupidGCM) Seal(_, _, _, _ []byte) []byte {
errExit()
return nil
}
func (g *StupidGCM) Open(_, _, _, _ []byte) ([]byte, error) {
errExit()
return nil, nil
}
func (g *StupidGCM) Wipe() {
errExit()
}

View File

@ -12,7 +12,7 @@ import (
// https://github.com/golang/go/blob/d2a80f3fb5b44450e0b304ac5a718f99c053d82a/src/os/file_posix.go#L243 // https://github.com/golang/go/blob/d2a80f3fb5b44450e0b304ac5a718f99c053d82a/src/os/file_posix.go#L243
// //
// This is needed because CIFS throws lots of EINTR errors: // This is needed because CIFS throws lots of EINTR errors:
// https://github.com/rfjakob/gocryptfs/issues/483 // https://github.com/rfjakob/gocryptfs/v2/issues/483
// //
// Don't use retryEINTR() with syscall.Close()! // Don't use retryEINTR() with syscall.Close()!
// See https://code.google.com/p/chromium/issues/detail?id=269623 . // See https://code.google.com/p/chromium/issues/detail?id=269623 .

View File

@ -19,7 +19,7 @@ const sizeofDirent = int(unsafe.Sizeof(unix.Dirent{}))
// maxReclen sanity check: Reclen should never be larger than this. // maxReclen sanity check: Reclen should never be larger than this.
// Due to padding between entries, it is 280 even on 32-bit architectures. // Due to padding between entries, it is 280 even on 32-bit architectures.
// See https://github.com/rfjakob/gocryptfs/issues/197 for details. // See https://github.com/rfjakob/gocryptfs/v2/issues/197 for details.
const maxReclen = 280 const maxReclen = 280
type DirEntry struct { type DirEntry struct {

View File

@ -1,7 +1,6 @@
package syscallcompat package syscallcompat
import ( import (
"bytes"
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -27,21 +26,6 @@ func Readlinkat(dirfd int, path string) (string, error) {
} }
} }
// Faccessat exists both in Linux and in MacOS 10.10+, but the Linux version
// DOES NOT support any flags. Emulate AT_SYMLINK_NOFOLLOW like glibc does.
func Faccessat(dirfd int, path string, mode uint32) error {
var st unix.Stat_t
err := Fstatat(dirfd, path, &st, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return err
}
if st.Mode&syscall.S_IFMT == syscall.S_IFLNK {
// Pretend that a symlink is always accessible
return nil
}
return unix.Faccessat(dirfd, path, mode, 0)
}
// Openat wraps the Openat syscall. // Openat wraps the Openat syscall.
// Retries on EINTR. // Retries on EINTR.
func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) { func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
@ -57,15 +41,6 @@ func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
return fd, err return fd, err
} }
// Fchownat syscall.
func Fchownat(dirfd int, path string, uid int, gid int, flags int) (err error) {
// Why would we ever want to call this without AT_SYMLINK_NOFOLLOW?
if flags&unix.AT_SYMLINK_NOFOLLOW == 0 {
flags |= unix.AT_SYMLINK_NOFOLLOW
}
return unix.Fchownat(dirfd, path, uid, gid, flags)
}
// Fstatat syscall. // Fstatat syscall.
// Retries on EINTR. // Retries on EINTR.
func Fstatat(dirfd int, path string, stat *unix.Stat_t, flags int) (err error) { func Fstatat(dirfd int, path string, stat *unix.Stat_t, flags int) (err error) {
@ -100,118 +75,3 @@ const XATTR_BUFSZ = XATTR_SIZE_MAX + 1024
// We try with a small buffer first - this one can be allocated on the stack. // We try with a small buffer first - this one can be allocated on the stack.
const XATTR_BUFSZ_SMALL = 500 const XATTR_BUFSZ_SMALL = 500
// Fgetxattr is a wrapper around unix.Fgetxattr that handles the buffer sizing.
func Fgetxattr(fd int, attr string) (val []byte, err error) {
fn := func(buf []byte) (int, error) {
return unix.Fgetxattr(fd, attr, buf)
}
return getxattrSmartBuf(fn)
}
// Lgetxattr is a wrapper around unix.Lgetxattr that handles the buffer sizing.
func Lgetxattr(path string, attr string) (val []byte, err error) {
fn := func(buf []byte) (int, error) {
return unix.Lgetxattr(path, attr, buf)
}
return getxattrSmartBuf(fn)
}
func getxattrSmartBuf(fn func(buf []byte) (int, error)) ([]byte, error) {
// Fastpaths. Important for security.capabilities, which gets queried a lot.
buf := make([]byte, XATTR_BUFSZ_SMALL)
sz, err := fn(buf)
// Non-existing xattr
if err == unix.ENODATA {
return nil, err
}
// Underlying fs does not support security.capabilities (example: tmpfs)
if err == unix.EOPNOTSUPP {
return nil, err
}
// Small xattr
if err == nil && sz < len(buf) {
goto out
}
// Generic slowpath
//
// If the buffer is too small to fit the value, Linux and MacOS react
// differently:
// Linux: returns an ERANGE error and "-1" bytes.
// MacOS: truncates the value and returns "size" bytes.
//
// We choose the simple approach of buffer that is bigger than the limit on
// Linux, and return an error for everything that is bigger (which can
// only happen on MacOS).
buf = make([]byte, XATTR_BUFSZ)
sz, err = fn(buf)
if err == syscall.ERANGE {
// Do NOT return ERANGE - the user might retry ad inifinitum!
return nil, syscall.EOVERFLOW
}
if err != nil {
return nil, err
}
if sz >= XATTR_SIZE_MAX {
return nil, syscall.EOVERFLOW
}
out:
// Copy only the actually used bytes to a new (smaller) buffer
// so "buf" never leaves the function and can be allocated on the stack.
val := make([]byte, sz)
copy(val, buf)
return val, nil
}
// Flistxattr is a wrapper for unix.Flistxattr that handles buffer sizing and
// parsing the returned blob to a string slice.
func Flistxattr(fd int) (attrs []string, err error) {
// See the buffer sizing comments in getxattrSmartBuf.
// TODO: smarter buffer sizing?
buf := make([]byte, XATTR_BUFSZ)
sz, err := unix.Flistxattr(fd, buf)
if err == syscall.ERANGE {
// Do NOT return ERANGE - the user might retry ad inifinitum!
return nil, syscall.EOVERFLOW
}
if err != nil {
return nil, err
}
if sz >= XATTR_SIZE_MAX {
return nil, syscall.EOVERFLOW
}
attrs = parseListxattrBlob(buf[:sz])
return attrs, nil
}
// Llistxattr is a wrapper for unix.Llistxattr that handles buffer sizing and
// parsing the returned blob to a string slice.
func Llistxattr(path string) (attrs []string, err error) {
// TODO: smarter buffer sizing?
buf := make([]byte, XATTR_BUFSZ)
sz, err := unix.Llistxattr(path, buf)
if err == syscall.ERANGE {
// Do NOT return ERANGE - the user might retry ad inifinitum!
return nil, syscall.EOVERFLOW
}
if err != nil {
return nil, err
}
if sz >= XATTR_SIZE_MAX {
return nil, syscall.EOVERFLOW
}
attrs = parseListxattrBlob(buf[:sz])
return attrs, nil
}
func parseListxattrBlob(buf []byte) (attrs []string) {
parts := bytes.Split(buf, []byte{0})
for _, part := range parts {
if len(part) == 0 {
// Last part is empty, ignore
continue
}
attrs = append(attrs, string(part))
}
return attrs
}

View File

@ -35,7 +35,7 @@ func EnospcPrealloc(fd int, off int64, len int64) (err error) {
} }
if err == syscall.EOPNOTSUPP { if err == syscall.EOPNOTSUPP {
// ZFS and ext3 do not support fallocate. Warn but continue anyway. // ZFS and ext3 do not support fallocate. Warn but continue anyway.
// https://github.com/rfjakob/gocryptfs/issues/22 // https://github.com/rfjakob/gocryptfs/v2/issues/22
return nil return nil
} }
return err return err

View File

@ -75,7 +75,7 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co
newVolume.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode) newVolume.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode)
newVolume.contentEnc = contentenc.New(newVolume.cryptoCore, contentenc.DefaultBS, forcedecode) newVolume.contentEnc = contentenc.New(newVolume.cryptoCore, contentenc.DefaultBS, forcedecode)
var badname []string var badname []string
newVolume.nameTransform = nametransform.New(newVolume.cryptoCore.EMECipher, true, true, badname) newVolume.nameTransform = nametransform.New(newVolume.cryptoCore.EMECipher, true, true, badname, false)
//copying rootCipherDir //copying rootCipherDir
var grcd strings.Builder var grcd strings.Builder
@ -156,7 +156,16 @@ func gcf_change_password(rootCipherDir string, oldPassword, givenScryptHash, new
//export gcf_create_volume //export gcf_create_volume
func gcf_create_volume(rootCipherDir string, password []byte, plaintextNames bool, logN int, creator string) bool { func gcf_create_volume(rootCipherDir string, password []byte, plaintextNames bool, logN int, creator string) bool {
err := configfile.Create(filepath.Join(rootCipherDir, configfile.ConfDefaultName), password, plaintextNames, logN, creator, false, false, nil, nil) err := configfile.Create(&configfile.CreateArgs{
Filename: filepath.Join(rootCipherDir, configfile.ConfDefaultName),
Password: password,
PlaintextNames: plaintextNames,
LogN: logN,
Creator: creator,
AESSIV: false,
DeterministicNames: false,
XChaCha20Poly1305: false,
})
if err == nil { if err == nil {
if plaintextNames { if plaintextNames {
return true return true