diff --git a/README.md b/README.md index 401bffc..0ad6bb2 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ File ext `*.__encfile`: ```json { + "name": "", "iv": "<16 bytes IV in base64>" } ``` diff --git a/encfs/file.go b/encfs/file.go index 92fc144..abeac9b 100644 --- a/encfs/file.go +++ b/encfs/file.go @@ -1,28 +1,121 @@ package encfs import ( + "crypto/rand" + "encoding/json" + "errors" + "io" "os" "syscall" "github.com/spf13/afero" ) -type EncFile struct { - isDir bool - closed bool - file *os.File +const EncFileExt = ".__encfile" + +var ( + ErrFileForbiddenFileExt = errors.New("File ext is forbidden") +) + +type EncFileMeta struct { + Name string `json:"name"` + Iv []byte `json:"iv"` } -func NewEncFile(file *os.File) (*EncFile, error) { +func newEncFileMeta(name string) (*EncFileMeta, error) { + iv := make([]byte, 16) + _, err := rand.Read(iv) + if err != nil { + return nil, err + } + encFileMeta := &EncFileMeta{ + Name: name, + Iv: iv, + } + encFileMetaName := name + EncFileExt + encFileMetaFile, err := os.Create(encFileMetaName) + if err != nil { + return nil, err + } + defer func() { + _ = encFileMetaFile.Close() + }() + encFileMetaBytes, err := marshalEncFileMeta(encFileMeta) + if err != nil { + return nil, err + } + _, err = encFileMetaFile.Write(encFileMetaBytes) + if err != nil { + return nil, err + } + return encFileMeta, nil +} + +func openEncFileMeta(name string) (*EncFileMeta, error) { + encFileMetaName := name + "." + EncFileExt + encFileMetaFile, err := os.Open(encFileMetaName) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + defer func() { + _ = encFileMetaFile.Close() + }() + encFileMetaBytes, err := io.ReadAll(encFileMetaFile) + if err != nil { + return nil, err + } + encFileMeta, err := unmarchalEncFileMeta(encFileMetaBytes) + if err != nil { + return nil, err + } + return encFileMeta, nil +} + +func marshalEncFileMeta(encFileMeata *EncFileMeta) ([]byte, error) { + return json.Marshal(encFileMeata) +} + +func unmarchalEncFileMeta(data []byte) (*EncFileMeta, error) { + var encFileMeta EncFileMeta + err := json.Unmarshal(data, &encFileMeta) + if err != nil { + return nil, err + } + return &encFileMeta, nil +} + +type EncFile struct { + isDir bool + closed bool + encFileMeta *EncFileMeta + file *os.File +} + +func NewEncFile(name string, file *os.File, isCreate bool) (*EncFile, error) { fileInfo, err := file.Stat() if err != nil { return nil, err } isDir := fileInfo.IsDir() + var encFileMeta *EncFileMeta = nil + if !isDir { + if isCreate { + encFileMeta, err = newEncFileMeta(name) + } else { + encFileMeta, err = openEncFileMeta(name) + } + if err != nil { + return nil, err + } + } return &EncFile{ - isDir: isDir, - closed: false, - file: file, + isDir: isDir, + closed: false, + encFileMeta: encFileMeta, + file: file, }, nil } diff --git a/encfs/fs.go b/encfs/fs.go index 9bd8896..43c0b95 100644 --- a/encfs/fs.go +++ b/encfs/fs.go @@ -2,6 +2,7 @@ package encfs import ( "os" + "strings" "time" "github.com/spf13/afero" @@ -18,13 +19,16 @@ func NewEncFs() afero.Fs { func (EncFs) Name() string { return "EncFs" } func (EncFs) Create(name string) (afero.File, error) { + if err := checkFileExt(name); err != nil { + return nil, err + } f, e := os.Create(name) if f == nil { // while this looks strange, we need to return a bare nil (of type nil) not // a nil value of type *os.File or nil won't be nil return nil, e } - return convertOsFileToEncFile(f, e) + return convertOsFileToEncFile(name, f, e, true) } func (EncFs) Mkdir(name string, perm os.FileMode) error { @@ -36,13 +40,16 @@ func (EncFs) MkdirAll(path string, perm os.FileMode) error { } func (EncFs) Open(name string) (afero.File, error) { + if err := checkFileExt(name); err != nil { + return nil, err + } f, e := os.Open(name) if f == nil { // while this looks strange, we need to return a bare nil (of type nil) not // a nil value of type *os.File or nil won't be nil return nil, e } - return convertOsFileToEncFile(f, e) + return convertOsFileToEncFile(name, f, e, false) } func (EncFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { @@ -99,12 +106,19 @@ func (EncFs) ReadlinkIfPossible(name string) (string, error) { return os.Readlink(name) } -func convertOsFileToEncFile(file *os.File, e error) (afero.File, error) { +func checkFileExt(name string) error { + if strings.HasSuffix(name, EncFileExt) { + return ErrFileForbiddenFileExt + } + return nil +} + +func convertOsFileToEncFile(name string, file *os.File, e error, isCreate bool) (afero.File, error) { if e != nil { return nil, e } // TODO add password, and IV etc ... - encFile, err := NewEncFile(file) + encFile, err := NewEncFile(name, file, isCreate) if err != nil { return nil, err } diff --git a/main.go b/main.go index a479161..35785a5 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "math" + "os" "git.hatter.ink/hatter/go-afero-encfs/encfs" "github.com/spf13/afero" @@ -94,6 +95,15 @@ func main() { return } fmt.Println("write len: ", len) + + f, err := os.Open("test2") + fmt.Println(f, "-", err, ", ", os.IsNotExist(err)) + + a, err := encFs.Open(".") + fs, err := a.Readdir(1000) + for _, ff := range fs { + fmt.Println(ff.Name()) + } } func generateCtrEncryptBytes(key, iv []byte, offset, len int64) ([]byte, error) {