Browse Source

libgocryptfs: update to gocryptfs v2.1

libgocryptfs
Hardcore Sushi 10 months ago
parent
commit
f0e45c7b7e
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
  1. 2
      directory.go
  2. 4
      helpers.go
  3. 153
      internal/configfile/config_file.go
  4. 39
      internal/configfile/feature_flags.go
  5. 19
      internal/configfile/scrypt.go
  6. 67
      internal/configfile/validate.go
  7. 14
      internal/contentenc/content.go
  8. 95
      internal/cryptocore/cryptocore.go
  9. 7
      internal/cryptocore/hkdf.go
  10. 8
      internal/nametransform/diriv.go
  11. 2
      internal/nametransform/longnames.go
  12. 14
      internal/nametransform/names.go
  13. 4
      internal/nametransform/perms.go
  14. 9
      internal/siv_aead/siv_aead.go
  15. 4
      internal/stupidgcm/prefer.go
  16. 2
      internal/stupidgcm/stupidgcm.go
  17. 52
      internal/stupidgcm/without_openssl.go
  18. 2
      internal/syscallcompat/eintr.go
  19. 2
      internal/syscallcompat/getdents_linux.go
  20. 140
      internal/syscallcompat/sys_common.go
  21. 2
      internal/syscallcompat/sys_linux.go
  22. 13
      volume.go

2
directory.go

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

4
helpers.go

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

153
internal/configfile/config_file.go

@ -5,9 +5,7 @@ package configfile
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"syscall"
"os"
@ -60,64 +58,66 @@ type ConfFile struct {
filename string
}
// randBytesDevRandom gets "n" random bytes from /dev/random or panics
func randBytesDevRandom(n int) []byte {
f, err := os.Open("/dev/random")
if err != nil {
log.Panic("Failed to open /dev/random: " + err.Error())
}
defer f.Close()
b := make([]byte, n)
_, err = io.ReadFull(f, b)
if err != nil {
log.Panic("Failed to read random bytes: " + err.Error())
}
return b
// CreateArgs exists because the argument list to Create became too long.
type CreateArgs struct {
Filename string
Password []byte
PlaintextNames bool
LogN int
Creator string
AESSIV bool
DeterministicNames bool
XChaCha20Poly1305 bool
}
// Create - create a new config with a random key encrypted with
// "password" and write it to "filename".
// Uses scrypt with cost parameter logN.
func Create(filename string, password []byte, plaintextNames bool,
logN int, creator string, aessiv bool, devrandom bool, fido2CredentialID []byte, fido2HmacSalt []byte) error {
var cf ConfFile
cf.filename = filename
cf.Creator = creator
cf.Version = contentenc.CurrentVersion
// Set feature flags
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagGCMIV128])
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagHKDF])
if plaintextNames {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagPlaintextNames])
} else {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagDirIV])
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagEMENames])
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagLongNames])
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagRaw64])
// "Password" and write it to "Filename".
// Uses scrypt with cost parameter "LogN".
func Create(args *CreateArgs) error {
cf := ConfFile{
filename: args.Filename,
Creator: args.Creator,
Version: contentenc.CurrentVersion,
}
if aessiv {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV])
// Feature flags
cf.setFeatureFlag(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 len(fido2CredentialID) > 0 {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2])
cf.FIDO2 = &FIDO2Params{
CredentialID: fido2CredentialID,
HMACSalt: fido2HmacSalt,
if args.PlaintextNames {
cf.setFeatureFlag(FlagPlaintextNames)
} else {
if !args.DeterministicNames {
cf.setFeatureFlag(FlagDirIV)
}
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
var key []byte
if devrandom {
key = randBytesDevRandom(cryptocore.KeyLen)
} else {
key = cryptocore.RandBytes(cryptocore.KeyLen)
}
key := cryptocore.RandBytes(cryptocore.KeyLen)
// Encrypt it using the password
// This sets ScryptObject and EncryptedKey
// 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 {
key[i] = 0
}
@ -175,39 +175,22 @@ func Load(filename string) (*ConfFile, error) {
return nil, err
}
if cf.Version != contentenc.CurrentVersion {
return nil, fmt.Errorf("Unsupported on-disk format %d", cf.Version)
}
// 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)
if err := cf.Validate(); err != nil {
return nil, exitcodes.NewErr(err.Error(), exitcodes.DeprecatedFS)
}
// All good
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
// password.
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".
// This way a password change atomically replaces the file.
func (cf *ConfFile) WriteFile() error {
if err := cf.Validate(); err != nil {
return err
}
tmp := cf.filename + ".tmp"
// 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)
@ -315,7 +301,7 @@ func (cf *ConfFile) WriteFile() error {
err = fd.Sync()
if err != nil {
// 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
syscall.Sync()
}
@ -340,3 +326,18 @@ func getKeyEncrypter(scryptHash []byte, useHKDF bool) *contentenc.ContentEnc {
ce := contentenc.New(cc, 4096, false)
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
}

39
internal/configfile/feature_flags.go

@ -11,7 +11,8 @@ const (
// This flag is mandatory since gocryptfs v1.0.
FlagEMENames
// 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
// FlagLongNames allows file names longer than 176 bytes.
FlagLongNames
@ -28,36 +29,26 @@ 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",
}
// 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,
FlagPlaintextNames: "PlaintextNames",
FlagDirIV: "DirIV",
FlagEMENames: "EMENames",
FlagGCMIV128: "GCMIV128",
FlagLongNames: "LongNames",
FlagAESSIV: "AESSIV",
FlagRaw64: "Raw64",
FlagHKDF: "HKDF",
FlagFIDO2: "FIDO2",
FlagXChaCha20Poly1305: "XChaCha20Poly1305",
}
// isFeatureFlagKnown verifies that we understand a feature flag.
func (cf *ConfFile) isFeatureFlagKnown(flag string) bool {
func isFeatureFlagKnown(flag string) bool {
for _, knownFlag := range knownFlags {
if knownFlag == flag {
return true

19
internal/configfile/scrypt.go

@ -1,6 +1,7 @@
package configfile
import (
"fmt"
"log"
"math"
"os"
@ -61,8 +62,9 @@ func NewScryptKDF(logN int) ScryptKDF {
// DeriveKey returns a new key from a supplied password.
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)
if err != nil {
log.Panicf("DeriveKey failed: %v", err)
@ -80,21 +82,22 @@ func (s *ScryptKDF) LogN() int {
// If not, it exists with an error message.
// This makes sure we do not get weak parameters passed through a
// rougue gocryptfs.conf.
func (s *ScryptKDF) validateParams() {
func (s *ScryptKDF) validateParams() error {
minN := 1 << scryptMinLogN
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 {
os.Exit(exitcodes.ScryptParams)
return fmt.Errorf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
}
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 {
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 {
os.Exit(exitcodes.ScryptParams)
return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
}
return nil
}

67
internal/configfile/validate.go

@ -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
}

14
internal/contentenc/content.go

@ -13,9 +13,6 @@ import (
"../stupidgcm"
)
// NonceMode determines how nonces are created.
type NonceMode int
const (
//value from FUSE doc
MAX_KERNEL_WRITE = 128 * 1024
@ -27,15 +24,6 @@ const (
// 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.
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.
@ -171,7 +159,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
nonce := ciphertext[:be.cryptoCore.IVLen]
if bytes.Equal(nonce, be.allZeroNonce) {
// 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
return nil, errors.New("all-zero nonce")
}

95
internal/cryptocore/cryptocore.go

@ -6,10 +6,11 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/sha512"
"fmt"
"log"
"runtime"
"golang.org/x/crypto/chacha20poly1305"
"github.com/rfjakob/eme"
"../siv_aead"
@ -17,36 +18,34 @@ import (
)
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
// 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
)
// AEADTypeEnum indicates the type of AEAD backend in use.
type AEADTypeEnum int
type AEADTypeEnum struct {
Name string
NonceSize int
}
const (
// 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
)
// BackendOpenSSL specifies the OpenSSL backend.
// "AES-GCM-256-OpenSSL" in gocryptfs -speed.
var BackendOpenSSL AEADTypeEnum = AEADTypeEnum{"AES-GCM-256-OpenSSL", 16}
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)
}
}
// 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.
type CryptoCore struct {
@ -58,7 +57,8 @@ type CryptoCore struct {
AEADBackend AEADTypeEnum
// GCM needs unique IVs (nonces)
IVGenerator *nonceGenerator
IVLen int
// IVLen in bytes
IVLen int
}
// 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).
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDecode bool) *CryptoCore {
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.
var emeCipher *eme.EMECipher
@ -103,12 +104,14 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
if useHKDF {
gcmKey = hkdfDerive(key, hkdfInfoGCMContent, KeyLen)
} else {
// Filesystems created by gocryptfs v0.7 through v1.2 don't use HKDF.
// Example: tests/example_filesystems/v0.9
gcmKey = append([]byte{}, key...)
}
switch aeadType {
case BackendOpenSSL:
if IVLen != 16 {
log.Panic("stupidgcm only supports 128-bit IVs")
if IVBitLen != 128 {
log.Panicf("stupidgcm only supports 128-bit IVs, you wanted %d", IVBitLen)
}
aeadCipher = stupidgcm.New(gcmKey, forceDecode)
case BackendGoGCM:
@ -116,7 +119,7 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
if err != nil {
log.Panic(err)
}
aeadCipher, err = cipher.NewGCMWithNonceSize(goGcmBlockCipher, IVLen)
aeadCipher, err = cipher.NewGCMWithNonceSize(goGcmBlockCipher, IVBitLen/8)
if err != nil {
log.Panic(err)
}
@ -125,9 +128,9 @@ func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDec
gcmKey[i] = 0
}
} else if aeadType == BackendAESSIV {
if IVLen != 16 {
// SIV supports any nonce size, but we only use 16.
log.Panic("AES-SIV must use 16-byte nonces")
if IVBitLen != 128 {
// SIV supports any nonce size, but we only use 128.
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
// 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 {
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.Name)
}
if aeadCipher.NonceSize()*8 != IVBitLen {
log.Panicf("Mismatched aeadCipher.NonceSize*8=%d and IVBitLen=%d bits",
aeadCipher.NonceSize()*8, IVBitLen)
}
return &CryptoCore{
EMECipher: emeCipher,
AEADCipher: aeadCipher,
AEADBackend: aeadType,
IVGenerator: &nonceGenerator{nonceLen: IVLen},
IVLen: IVLen,
IVGenerator: &nonceGenerator{nonceLen: IVBitLen / 8},
IVLen: IVBitLen / 8,
}
}

7
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

8
internal/nametransform/diriv.go

@ -22,7 +22,11 @@ const (
// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".
// Using the dirfd makes it immune to concurrent renames of the directory.
// 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,
syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil {
@ -63,7 +67,7 @@ func WriteDirIVAt(dirfd int) error {
iv := cryptocore.RandBytes(DirIVLen)
// 0400 permissions: gocryptfs.diriv should never be modified after creation.
// 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)
if err != nil {
return err

2
internal/nametransform/longnames.go

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

14
internal/nametransform/names.go

@ -23,20 +23,22 @@ type NameTransform struct {
// on the Raw64 feature flag
B64 *base64.Encoding
// Patterns to bypass decryption
badnamePatterns []string
badnamePatterns []string
deterministicNames bool
}
// 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
if raw64 {
b64 = base64.RawURLEncoding
}
return &NameTransform{
emeCipher: e,
longNames: longNames,
B64: b64,
badnamePatterns: badname,
emeCipher: e,
longNames: longNames,
B64: b64,
badnamePatterns: badname,
deterministicNames: deterministicNames,
}
}

4
internal/nametransform/perms.go

@ -6,14 +6,14 @@ const (
// never chmod'ed or chown'ed.
//
// 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
// owner must explicitly chmod it to permit access.
//
// World-readable so an encrypted directory can be copied by the non-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
// Permissions for gocryptfs.longname.[sha256].name files.

9
internal/siv_aead/siv_aead.go

@ -19,6 +19,11 @@ const (
// KeyLen is the required key length. The SIV algorithm supports other lengths,
// but we only support 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.
@ -42,11 +47,11 @@ func new2(keyIn []byte) cipher.AEAD {
func (s *sivAead) NonceSize() int {
// SIV supports any nonce size, but in gocryptfs we exclusively use 16.
return 16
return NonceSize
}
func (s *sivAead) Overhead() int {
return 16
return Overhead
}
// Seal encrypts "in" using "nonce" and "authData" and appends the result to "dst"

4
internal/stupidgcm/prefer.go

@ -14,7 +14,7 @@ import (
// 2) Is ARM64 && has AES instructions && Go is v1.11 or higher
// (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.
func PreferOpenSSL() bool {
if BuiltWithoutOpenssl {
@ -26,7 +26,7 @@ func PreferOpenSSL() bool {
return false
}
// 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" {
return false
}

2
internal/stupidgcm/stupidgcm.go

@ -224,7 +224,7 @@ func (g *StupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) {
if res != 1 {
// 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
if g.forceDecode == true {
if g.forceDecode {
return append(dst, buf...), ErrAuth
}
return nil, ErrAuth

52
internal/stupidgcm/without_openssl.go

@ -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()
}

2
internal/syscallcompat/eintr.go

@ -12,7 +12,7 @@ import (
// https://github.com/golang/go/blob/d2a80f3fb5b44450e0b304ac5a718f99c053d82a/src/os/file_posix.go#L243
//
// 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()!
// See https://code.google.com/p/chromium/issues/detail?id=269623 .

2
internal/syscallcompat/getdents_linux.go

@ -19,7 +19,7 @@ const sizeofDirent = int(unsafe.Sizeof(unix.Dirent{}))
// maxReclen sanity check: Reclen should never be larger than this.
// 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
type DirEntry struct {

140
internal/syscallcompat/sys_common.go

@ -1,7 +1,6 @@
package syscallcompat
import (
"bytes"
"syscall"
"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.
// Retries on EINTR.
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
}
// 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.
// Retries on EINTR.
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.
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
}

2
internal/syscallcompat/sys_linux.go

@ -35,7 +35,7 @@ func EnospcPrealloc(fd int, off int64, len int64) (err error) {
}
if err == syscall.EOPNOTSUPP {
// 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 err

13
volume.go

@ -75,7 +75,7 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co
newVolume.cryptoCore = cryptocore.New(masterkey, cryptoBackend, contentenc.DefaultIVBits, true, forcedecode)
newVolume.contentEnc = contentenc.New(newVolume.cryptoCore, contentenc.DefaultBS, forcedecode)
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
var grcd strings.Builder
@ -156,7 +156,16 @@ func gcf_change_password(rootCipherDir string, oldPassword, givenScryptHash, new
//export gcf_create_volume
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 plaintextNames {
return true

Loading…
Cancel
Save