libgocryptfs: update to gocryptfs v2.1
This commit is contained in:
commit
f0e45c7b7e
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
67
internal/configfile/validate.go
Normal file
67
internal/configfile/validate.go
Normal 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
|
||||||
|
}
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
|
||||||
}
|
|
@ -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 .
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
13
volume.go
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.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
|
||||||
|
Loading…
Reference in New Issue
Block a user