aboutsummaryrefslogtreecommitdiff
path: root/internal/nametransform/diriv.go
blob: e4670c8e9d2aa7ee2f9f891f124bebd64025ac11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package nametransform

import (
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/rfjakob/gocryptfs/internal/cryptocore"
	"github.com/rfjakob/gocryptfs/internal/syscallcompat"
	"github.com/rfjakob/gocryptfs/internal/tlog"
)

const (
	// DirIVLen is identical to AES block size
	DirIVLen = 16
	// DirIVFilename is the filename used to store directory IV.
	// Exported because we have to ignore this name in directory listing.
	DirIVFilename = "gocryptfs.diriv"
)

// ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path)
// This function is exported because it allows for an efficient readdir implementation.
func ReadDirIV(dir string) (iv []byte, err error) {
	fd, err := os.Open(filepath.Join(dir, DirIVFilename))
	if err != nil {
		// Note: getting errors here is normal because of concurrent deletes.
		return nil, err
	}
	defer fd.Close()
	return fdReadDirIV(fd)
}

// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".
// Using the dirfd makes it immune to concurrent renames of the directory.
func ReadDirIVAt(dirfd *os.File) (iv []byte, err error) {
	fdRaw, err := syscallcompat.Openat(int(dirfd.Fd()), DirIVFilename, syscall.O_RDONLY, 0)
	if err != nil {
		tlog.Warn.Printf("ReadDirIVAt: opening %q in dir %q failed: %v",
			DirIVFilename, dirfd.Name(), err)
		return nil, err
	}
	fd := os.NewFile(uintptr(fdRaw), DirIVFilename)
	defer fd.Close()
	return fdReadDirIV(fd)
}

// fdReadDirIV reads and verifies the DirIV from an opened gocryptfs.diriv file.
func fdReadDirIV(fd *os.File) (iv []byte, err error) {
	// We want to detect if the file is bigger than DirIVLen, so
	// make the buffer 1 byte bigger than necessary.
	iv = make([]byte, DirIVLen+1)
	n, err := fd.Read(iv)
	if err != nil && err != io.EOF {
		tlog.Warn.Printf("ReadDirIVAt: Read failed: %v", err)
		return nil, err
	}
	iv = iv[0:n]
	if len(iv) != DirIVLen {
		tlog.Warn.Printf("ReadDirIVAt: wanted %d bytes, got %d. Returning EINVAL.", DirIVLen, len(iv))
		return nil, syscall.EINVAL
	}
	return iv, nil
}

// WriteDirIV - create diriv file inside "dir" (absolute ciphertext path)
// This function is exported because it is used from pathfs_frontend, main,
// and also the automated tests.
func WriteDirIV(dir string) error {
	iv := cryptocore.RandBytes(DirIVLen)
	file := filepath.Join(dir, DirIVFilename)
	err := ioutil.WriteFile(file, iv, 0400)
	if err != nil {
		tlog.Warn.Printf("WriteDirIV: %v", err)
	}
	return err
}

// EncryptPathDirIV - encrypt relative plaintext path using EME with DirIV.
// Components that are longer than 255 bytes are hashed if be.longnames == true.
func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) {
	// Empty string means root directory
	if plainPath == "" {
		return plainPath, nil
	}
	// Reject names longer than 255 bytes already here. This relieves everybody
	// who uses hashed long names from checking for that later.
	baseName := filepath.Base(plainPath)
	if len(baseName) > syscall.NAME_MAX {
		return "", syscall.ENAMETOOLONG
	}
	// Check if the DirIV is cached
	parentDir := filepath.Dir(plainPath)
	iv, cParentDir := be.DirIVCache.lookup(parentDir)
	if iv != nil {
		cBaseName := be.EncryptName(baseName, iv)
		if be.longNames && len(cBaseName) > syscall.NAME_MAX {
			cBaseName = be.HashLongName(cBaseName)
		}
		cipherPath = filepath.Join(cParentDir, cBaseName)
		return cipherPath, nil
	}
	// Not cached - walk the directory tree
	var wd = rootDir
	var encryptedNames []string
	plainNames := strings.Split(plainPath, "/")
	for _, plainName := range plainNames {
		iv, err = ReadDirIV(wd)
		if err != nil {
			return "", err
		}
		encryptedName := be.EncryptName(plainName, iv)
		if be.longNames && len(encryptedName) > syscall.NAME_MAX {
			encryptedName = be.HashLongName(encryptedName)
		}
		encryptedNames = append(encryptedNames, encryptedName)
		wd = filepath.Join(wd, encryptedName)
	}
	cipherPath = filepath.Join(encryptedNames...)
	// Cache the final DirIV
	cParentDir = filepath.Dir(cipherPath)
	be.DirIVCache.store(parentDir, iv, cParentDir)
	return cipherPath, nil
}